diff options
| author | 2023-03-08 17:12:47 -0500 | |
|---|---|---|
| committer | 2023-06-03 00:05:39 -0700 | |
| commit | a1c57de466855bf826262a1613699ce9c468cadc (patch) | |
| tree | 75acd0979c9542c8efdfcb7922e3f09471ab3368 /src/android | |
| parent | android: Remove DividerItemDecoration (diff) | |
| download | yuzu-a1c57de466855bf826262a1613699ce9c468cadc.tar.gz yuzu-a1c57de466855bf826262a1613699ce9c468cadc.tar.xz yuzu-a1c57de466855bf826262a1613699ce9c468cadc.zip | |
android: Convert InputOverlay to Kotlin
Diffstat (limited to 'src/android')
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java | 656 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 886 |
2 files changed, 886 insertions, 656 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java deleted file mode 100644 index 74119c398..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java +++ /dev/null | |||
| @@ -1,656 +0,0 @@ | |||
| 1 | /** | ||
| 2 | * Copyright 2013 Dolphin Emulator Project | ||
| 3 | * Licensed under GPLv2+ | ||
| 4 | * Refer to the license.txt file included. | ||
| 5 | */ | ||
| 6 | |||
| 7 | package org.yuzu.yuzu_emu.overlay; | ||
| 8 | |||
| 9 | import android.app.Activity; | ||
| 10 | import android.content.Context; | ||
| 11 | import android.content.SharedPreferences; | ||
| 12 | import android.content.res.Configuration; | ||
| 13 | import android.content.res.Resources; | ||
| 14 | import android.graphics.Bitmap; | ||
| 15 | import android.graphics.BitmapFactory; | ||
| 16 | import android.graphics.Canvas; | ||
| 17 | import android.graphics.Rect; | ||
| 18 | import android.graphics.drawable.BitmapDrawable; | ||
| 19 | import android.graphics.drawable.Drawable; | ||
| 20 | import android.graphics.drawable.VectorDrawable; | ||
| 21 | import android.hardware.Sensor; | ||
| 22 | import android.hardware.SensorEvent; | ||
| 23 | import android.hardware.SensorEventListener; | ||
| 24 | import android.hardware.SensorManager; | ||
| 25 | import android.preference.PreferenceManager; | ||
| 26 | import android.util.AttributeSet; | ||
| 27 | import android.util.DisplayMetrics; | ||
| 28 | import android.view.Display; | ||
| 29 | import android.view.MotionEvent; | ||
| 30 | import android.view.SurfaceView; | ||
| 31 | import android.view.View; | ||
| 32 | import android.view.View.OnTouchListener; | ||
| 33 | |||
| 34 | import androidx.core.content.ContextCompat; | ||
| 35 | |||
| 36 | import org.yuzu.yuzu_emu.NativeLibrary; | ||
| 37 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType; | ||
| 38 | import org.yuzu.yuzu_emu.NativeLibrary.StickType; | ||
| 39 | import org.yuzu.yuzu_emu.R; | ||
| 40 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings; | ||
| 41 | |||
| 42 | import java.util.HashSet; | ||
| 43 | import java.util.Set; | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Draws the interactive input overlay on top of the | ||
| 47 | * {@link SurfaceView} that is rendering emulation. | ||
| 48 | */ | ||
| 49 | public final class InputOverlay extends SurfaceView implements OnTouchListener, SensorEventListener { | ||
| 50 | private final Set<InputOverlayDrawableButton> overlayButtons = new HashSet<>(); | ||
| 51 | private final Set<InputOverlayDrawableDpad> overlayDpads = new HashSet<>(); | ||
| 52 | private final Set<InputOverlayDrawableJoystick> overlayJoysticks = new HashSet<>(); | ||
| 53 | |||
| 54 | private boolean mIsInEditMode = false; | ||
| 55 | |||
| 56 | private SharedPreferences mPreferences; | ||
| 57 | |||
| 58 | private float[] gyro = new float[3]; | ||
| 59 | private float[] accel = new float[3]; | ||
| 60 | |||
| 61 | private long motionTimestamp; | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Constructor | ||
| 65 | * | ||
| 66 | * @param context The current {@link Context}. | ||
| 67 | * @param attrs {@link AttributeSet} for parsing XML attributes. | ||
| 68 | */ | ||
| 69 | public InputOverlay(Context context, AttributeSet attrs) { | ||
| 70 | super(context, attrs); | ||
| 71 | |||
| 72 | mPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); | ||
| 73 | if (!mPreferences.getBoolean("OverlayInit", false)) { | ||
| 74 | defaultOverlay(); | ||
| 75 | } | ||
| 76 | |||
| 77 | // Load the controls. | ||
| 78 | refreshControls(); | ||
| 79 | |||
| 80 | // Set the on motion sensor listener. | ||
| 81 | setMotionSensorListener(context); | ||
| 82 | |||
| 83 | // Set the on touch listener. | ||
| 84 | setOnTouchListener(this); | ||
| 85 | |||
| 86 | // Force draw | ||
| 87 | setWillNotDraw(false); | ||
| 88 | |||
| 89 | // Request focus for the overlay so it has priority on presses. | ||
| 90 | requestFocus(); | ||
| 91 | } | ||
| 92 | |||
| 93 | private void setMotionSensorListener(Context context) { | ||
| 94 | SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); | ||
| 95 | Sensor gyro_sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); | ||
| 96 | Sensor accel_sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); | ||
| 97 | |||
| 98 | if (gyro_sensor != null) { | ||
| 99 | sensorManager.registerListener(this, gyro_sensor, SensorManager.SENSOR_DELAY_GAME); | ||
| 100 | } | ||
| 101 | if (accel_sensor != null) { | ||
| 102 | sensorManager.registerListener(this, accel_sensor, SensorManager.SENSOR_DELAY_GAME); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | |||
| 107 | /** | ||
| 108 | * Resizes a {@link Bitmap} by a given scale factor | ||
| 109 | * | ||
| 110 | * @param vectorDrawable The {@link Bitmap} to scale. | ||
| 111 | * @param scale The scale factor for the bitmap. | ||
| 112 | * @return The scaled {@link Bitmap} | ||
| 113 | */ | ||
| 114 | private static Bitmap getBitmap(VectorDrawable vectorDrawable, float scale) { | ||
| 115 | Bitmap bitmap = Bitmap.createBitmap((int) (vectorDrawable.getIntrinsicWidth() * scale), | ||
| 116 | (int) (vectorDrawable.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888); | ||
| 117 | Canvas canvas = new Canvas(bitmap); | ||
| 118 | vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); | ||
| 119 | vectorDrawable.draw(canvas); | ||
| 120 | return bitmap; | ||
| 121 | } | ||
| 122 | |||
| 123 | private static Bitmap getBitmap(Context context, int drawableId, float scale) { | ||
| 124 | Drawable drawable = ContextCompat.getDrawable(context, drawableId); | ||
| 125 | if (drawable instanceof BitmapDrawable) { | ||
| 126 | return BitmapFactory.decodeResource(context.getResources(), drawableId); | ||
| 127 | } else if (drawable instanceof VectorDrawable) { | ||
| 128 | return getBitmap((VectorDrawable) drawable, scale); | ||
| 129 | } else { | ||
| 130 | throw new IllegalArgumentException("unsupported drawable type"); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /** | ||
| 135 | * Initializes an InputOverlayDrawableButton, given by resId, with all of the | ||
| 136 | * parameters set for it to be properly shown on the InputOverlay. | ||
| 137 | * <p> | ||
| 138 | * This works due to the way the X and Y coordinates are stored within | ||
| 139 | * the {@link SharedPreferences}. | ||
| 140 | * <p> | ||
| 141 | * In the input overlay configuration menu, | ||
| 142 | * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay). | ||
| 143 | * the X and Y coordinates of the button at the END of its touch event | ||
| 144 | * (when you remove your finger/stylus from the touchscreen) are then stored | ||
| 145 | * within a SharedPreferences instance so that those values can be retrieved here. | ||
| 146 | * <p> | ||
| 147 | * This has a few benefits over the conventional way of storing the values | ||
| 148 | * (ie. within the yuzu ini file). | ||
| 149 | * <ul> | ||
| 150 | * <li>No native calls</li> | ||
| 151 | * <li>Keeps Android-only values inside the Android environment</li> | ||
| 152 | * </ul> | ||
| 153 | * <p> | ||
| 154 | * Technically no modifications should need to be performed on the returned | ||
| 155 | * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait | ||
| 156 | * for Android to call the onDraw method. | ||
| 157 | * | ||
| 158 | * @param context The current {@link Context}. | ||
| 159 | * @param defaultResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Default State). | ||
| 160 | * @param pressedResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Pressed State). | ||
| 161 | * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. | ||
| 162 | * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set. | ||
| 163 | */ | ||
| 164 | private static InputOverlayDrawableButton initializeOverlayButton(Context context, | ||
| 165 | int defaultResId, int pressedResId, int buttonId, String orientation) { | ||
| 166 | // Resources handle for fetching the initial Drawable resource. | ||
| 167 | final Resources res = context.getResources(); | ||
| 168 | |||
| 169 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. | ||
| 170 | final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); | ||
| 171 | |||
| 172 | // Decide scale based on button ID and user preference | ||
| 173 | float scale; | ||
| 174 | |||
| 175 | switch (buttonId) { | ||
| 176 | case ButtonType.BUTTON_HOME: | ||
| 177 | case ButtonType.BUTTON_CAPTURE: | ||
| 178 | case ButtonType.BUTTON_PLUS: | ||
| 179 | case ButtonType.BUTTON_MINUS: | ||
| 180 | scale = 0.35f; | ||
| 181 | break; | ||
| 182 | case ButtonType.TRIGGER_L: | ||
| 183 | case ButtonType.TRIGGER_R: | ||
| 184 | case ButtonType.TRIGGER_ZL: | ||
| 185 | case ButtonType.TRIGGER_ZR: | ||
| 186 | scale = 0.38f; | ||
| 187 | break; | ||
| 188 | default: | ||
| 189 | scale = 0.43f; | ||
| 190 | break; | ||
| 191 | } | ||
| 192 | |||
| 193 | scale *= (sPrefs.getInt("controlScale", 50) + 50); | ||
| 194 | scale /= 100; | ||
| 195 | |||
| 196 | // Initialize the InputOverlayDrawableButton. | ||
| 197 | final Bitmap defaultStateBitmap = getBitmap(context, defaultResId, scale); | ||
| 198 | final Bitmap pressedStateBitmap = getBitmap(context, pressedResId, scale); | ||
| 199 | final InputOverlayDrawableButton overlayDrawable = | ||
| 200 | new InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId); | ||
| 201 | |||
| 202 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | ||
| 203 | // These were set in the input overlay configuration menu. | ||
| 204 | String xKey; | ||
| 205 | String yKey; | ||
| 206 | |||
| 207 | xKey = buttonId + orientation + "-X"; | ||
| 208 | yKey = buttonId + orientation + "-Y"; | ||
| 209 | |||
| 210 | int drawableX = (int) sPrefs.getFloat(xKey, 0f); | ||
| 211 | int drawableY = (int) sPrefs.getFloat(yKey, 0f); | ||
| 212 | |||
| 213 | int width = overlayDrawable.getWidth(); | ||
| 214 | int height = overlayDrawable.getHeight(); | ||
| 215 | |||
| 216 | // Now set the bounds for the InputOverlayDrawableButton. | ||
| 217 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be. | ||
| 218 | overlayDrawable.setBounds(drawableX - (width / 2), drawableY - (height / 2), drawableX + (width / 2), drawableY + (height / 2)); | ||
| 219 | |||
| 220 | // Need to set the image's position | ||
| 221 | overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2)); | ||
| 222 | |||
| 223 | return overlayDrawable; | ||
| 224 | } | ||
| 225 | |||
| 226 | /** | ||
| 227 | * Initializes an {@link InputOverlayDrawableDpad} | ||
| 228 | * | ||
| 229 | * @param context The current {@link Context}. | ||
| 230 | * @param defaultResId The {@link Bitmap} resource ID of the default sate. | ||
| 231 | * @param pressedOneDirectionResId The {@link Bitmap} resource ID of the pressed sate in one direction. | ||
| 232 | * @param pressedTwoDirectionsResId The {@link Bitmap} resource ID of the pressed sate in two directions. | ||
| 233 | * @param buttonUp Identifier for the up button. | ||
| 234 | * @param buttonDown Identifier for the down button. | ||
| 235 | * @param buttonLeft Identifier for the left button. | ||
| 236 | * @param buttonRight Identifier for the right button. | ||
| 237 | * @return the initialized {@link InputOverlayDrawableDpad} | ||
| 238 | */ | ||
| 239 | private static InputOverlayDrawableDpad initializeOverlayDpad(Context context, | ||
| 240 | int defaultResId, | ||
| 241 | int pressedOneDirectionResId, | ||
| 242 | int pressedTwoDirectionsResId, | ||
| 243 | int buttonUp, | ||
| 244 | int buttonDown, | ||
| 245 | int buttonLeft, | ||
| 246 | int buttonRight, | ||
| 247 | String orientation) { | ||
| 248 | // Resources handle for fetching the initial Drawable resource. | ||
| 249 | final Resources res = context.getResources(); | ||
| 250 | |||
| 251 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad. | ||
| 252 | final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); | ||
| 253 | |||
| 254 | // Decide scale based on button ID and user preference | ||
| 255 | float scale = 0.40f; | ||
| 256 | |||
| 257 | scale *= (sPrefs.getInt("controlScale", 50) + 50); | ||
| 258 | scale /= 100; | ||
| 259 | |||
| 260 | // Initialize the InputOverlayDrawableDpad. | ||
| 261 | final Bitmap defaultStateBitmap = getBitmap(context, defaultResId, scale); | ||
| 262 | final Bitmap pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId, | ||
| 263 | scale); | ||
| 264 | final Bitmap pressedTwoDirectionsStateBitmap = getBitmap(context, pressedTwoDirectionsResId, | ||
| 265 | scale); | ||
| 266 | final InputOverlayDrawableDpad overlayDrawable = | ||
| 267 | new InputOverlayDrawableDpad(res, defaultStateBitmap, | ||
| 268 | pressedOneDirectionStateBitmap, pressedTwoDirectionsStateBitmap, | ||
| 269 | buttonUp, buttonDown, buttonLeft, buttonRight); | ||
| 270 | |||
| 271 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. | ||
| 272 | // These were set in the input overlay configuration menu. | ||
| 273 | int drawableX = (int) sPrefs.getFloat(buttonUp + orientation + "-X", 0f); | ||
| 274 | int drawableY = (int) sPrefs.getFloat(buttonUp + orientation + "-Y", 0f); | ||
| 275 | |||
| 276 | int width = overlayDrawable.getWidth(); | ||
| 277 | int height = overlayDrawable.getHeight(); | ||
| 278 | |||
| 279 | // Now set the bounds for the InputOverlayDrawableDpad. | ||
| 280 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be. | ||
| 281 | overlayDrawable.setBounds(drawableX - (width / 2), drawableY - (height / 2), drawableX + (width / 2), drawableY + (height / 2)); | ||
| 282 | |||
| 283 | // Need to set the image's position | ||
| 284 | overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2)); | ||
| 285 | |||
| 286 | return overlayDrawable; | ||
| 287 | } | ||
| 288 | |||
| 289 | /** | ||
| 290 | * Initializes an {@link InputOverlayDrawableJoystick} | ||
| 291 | * | ||
| 292 | * @param context The current {@link Context} | ||
| 293 | * @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds). | ||
| 294 | * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around). | ||
| 295 | * @param pressedResInner Resource ID for the pressed inner image of the joystick. | ||
| 296 | * @param joystick Identifier for which joystick this is. | ||
| 297 | * @param button Identifier for which joystick button this is. | ||
| 298 | * @return the initialized {@link InputOverlayDrawableJoystick}. | ||
| 299 | */ | ||
| 300 | private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, | ||
| 301 | int resOuter, int defaultResInner, int pressedResInner, int joystick, int button, String orientation) { | ||
| 302 | // Resources handle for fetching the initial Drawable resource. | ||
| 303 | final Resources res = context.getResources(); | ||
| 304 | |||
| 305 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick. | ||
| 306 | final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); | ||
| 307 | |||
| 308 | // Decide scale based on user preference | ||
| 309 | float scale = 0.40f; | ||
| 310 | scale *= (sPrefs.getInt("controlScale", 50) + 50); | ||
| 311 | scale /= 100; | ||
| 312 | |||
| 313 | // Initialize the InputOverlayDrawableJoystick. | ||
| 314 | final Bitmap bitmapOuter = getBitmap(context, resOuter, scale); | ||
| 315 | final Bitmap bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f); | ||
| 316 | final Bitmap bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f); | ||
| 317 | |||
| 318 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | ||
| 319 | // These were set in the input overlay configuration menu. | ||
| 320 | int drawableX = (int) sPrefs.getFloat(button + orientation + "-X", 0f); | ||
| 321 | int drawableY = (int) sPrefs.getFloat(button + orientation + "-Y", 0f); | ||
| 322 | |||
| 323 | float outerScale = 1.66f; | ||
| 324 | |||
| 325 | // Now set the bounds for the InputOverlayDrawableJoystick. | ||
| 326 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be. | ||
| 327 | int outerSize = bitmapOuter.getWidth(); | ||
| 328 | Rect outerRect = new Rect(drawableX - (outerSize / 2), drawableY - (outerSize / 2), drawableX + (outerSize / 2), drawableY + (outerSize / 2)); | ||
| 329 | Rect innerRect = new Rect(0, 0, (int) (outerSize / outerScale), (int) (outerSize / outerScale)); | ||
| 330 | |||
| 331 | // Send the drawableId to the joystick so it can be referenced when saving control position. | ||
| 332 | final InputOverlayDrawableJoystick overlayDrawable | ||
| 333 | = new InputOverlayDrawableJoystick(res, bitmapOuter, | ||
| 334 | bitmapInnerDefault, bitmapInnerPressed, | ||
| 335 | outerRect, innerRect, joystick, button); | ||
| 336 | |||
| 337 | // Need to set the image's position | ||
| 338 | overlayDrawable.setPosition(drawableX, drawableY); | ||
| 339 | |||
| 340 | return overlayDrawable; | ||
| 341 | } | ||
| 342 | |||
| 343 | @Override | ||
| 344 | public void draw(Canvas canvas) { | ||
| 345 | super.draw(canvas); | ||
| 346 | |||
| 347 | for (InputOverlayDrawableButton button : overlayButtons) { | ||
| 348 | button.draw(canvas); | ||
| 349 | } | ||
| 350 | |||
| 351 | for (InputOverlayDrawableDpad dpad : overlayDpads) { | ||
| 352 | dpad.draw(canvas); | ||
| 353 | } | ||
| 354 | |||
| 355 | for (InputOverlayDrawableJoystick joystick : overlayJoysticks) { | ||
| 356 | joystick.draw(canvas); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | @Override | ||
| 361 | public boolean onTouch(View v, MotionEvent event) { | ||
| 362 | if (isInEditMode()) { | ||
| 363 | return onTouchWhileEditing(event); | ||
| 364 | } | ||
| 365 | boolean should_update_view = false; | ||
| 366 | for (InputOverlayDrawableButton button : overlayButtons) { | ||
| 367 | if (!button.updateStatus(event)) { | ||
| 368 | continue; | ||
| 369 | } | ||
| 370 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, button.getId(), button.getStatus()); | ||
| 371 | should_update_view = true; | ||
| 372 | } | ||
| 373 | |||
| 374 | for (InputOverlayDrawableDpad dpad : overlayDpads) { | ||
| 375 | if (!dpad.updateStatus(event, EmulationMenuSettings.getDpadSlideEnable())) { | ||
| 376 | continue; | ||
| 377 | } | ||
| 378 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getUpId(), dpad.getUpStatus()); | ||
| 379 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getDownId(), dpad.getDownStatus()); | ||
| 380 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getLeftId(), dpad.getLeftStatus()); | ||
| 381 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, dpad.getRightId(), dpad.getRightStatus()); | ||
| 382 | should_update_view = true; | ||
| 383 | } | ||
| 384 | |||
| 385 | for (InputOverlayDrawableJoystick joystick : overlayJoysticks) { | ||
| 386 | if (!joystick.updateStatus(event)) { | ||
| 387 | continue; | ||
| 388 | } | ||
| 389 | int axisID = joystick.getJoystickId(); | ||
| 390 | NativeLibrary.onGamePadJoystickEvent(NativeLibrary.Player1Device, axisID, joystick.getXAxis(), joystick.getYAxis()); | ||
| 391 | NativeLibrary.onGamePadButtonEvent(NativeLibrary.Player1Device, joystick.getButtonId(), joystick.getButtonStatus()); | ||
| 392 | should_update_view = true; | ||
| 393 | } | ||
| 394 | |||
| 395 | if (should_update_view) { | ||
| 396 | invalidate(); | ||
| 397 | } | ||
| 398 | |||
| 399 | if (!mPreferences.getBoolean("isTouchEnabled", true)) { | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | |||
| 403 | int pointerIndex = event.getActionIndex(); | ||
| 404 | int xPosition = (int) event.getX(pointerIndex); | ||
| 405 | int yPosition = (int) event.getY(pointerIndex); | ||
| 406 | int pointerId = event.getPointerId(pointerIndex); | ||
| 407 | int motion_event = event.getAction() & MotionEvent.ACTION_MASK; | ||
| 408 | boolean isActionDown = motion_event == MotionEvent.ACTION_DOWN || motion_event == MotionEvent.ACTION_POINTER_DOWN; | ||
| 409 | boolean isActionMove = motion_event == MotionEvent.ACTION_MOVE; | ||
| 410 | boolean isActionUp = motion_event == MotionEvent.ACTION_UP || motion_event == MotionEvent.ACTION_POINTER_UP; | ||
| 411 | |||
| 412 | if (isActionDown && !isTouchInputConsumed(pointerId)) { | ||
| 413 | NativeLibrary.onTouchPressed(pointerId, xPosition, yPosition); | ||
| 414 | } | ||
| 415 | |||
| 416 | if (isActionMove) { | ||
| 417 | for (int i = 0; i < event.getPointerCount(); i++) { | ||
| 418 | int fingerId = event.getPointerId(i); | ||
| 419 | if (isTouchInputConsumed(fingerId)) { | ||
| 420 | continue; | ||
| 421 | } | ||
| 422 | NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i)); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | if (isActionUp && !isTouchInputConsumed(pointerId)) { | ||
| 427 | NativeLibrary.onTouchReleased(pointerId); | ||
| 428 | } | ||
| 429 | |||
| 430 | return true; | ||
| 431 | } | ||
| 432 | |||
| 433 | private boolean isTouchInputConsumed(int track_id) { | ||
| 434 | for (InputOverlayDrawableButton button : overlayButtons) { | ||
| 435 | if (button.getTrackId() == track_id) { | ||
| 436 | return true; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | for (InputOverlayDrawableDpad dpad : overlayDpads) { | ||
| 440 | if (dpad.getTrackId() == track_id) { | ||
| 441 | return true; | ||
| 442 | } | ||
| 443 | } | ||
| 444 | for (InputOverlayDrawableJoystick joystick : overlayJoysticks) { | ||
| 445 | if (joystick.getTrackId() == track_id) { | ||
| 446 | return true; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | return false; | ||
| 450 | } | ||
| 451 | |||
| 452 | public boolean onTouchWhileEditing(MotionEvent event) { | ||
| 453 | // TODO: Reimplement this | ||
| 454 | return true; | ||
| 455 | } | ||
| 456 | |||
| 457 | @Override | ||
| 458 | public void onSensorChanged(SensorEvent event) { | ||
| 459 | if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { | ||
| 460 | accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH; | ||
| 461 | accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH; | ||
| 462 | accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH; | ||
| 463 | } | ||
| 464 | |||
| 465 | if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { | ||
| 466 | // Investigate why sensor value is off by 12x | ||
| 467 | gyro[0] = event.values[1] / 12.0f; | ||
| 468 | gyro[1] = -event.values[0] / 12.0f; | ||
| 469 | gyro[2] = event.values[2] / 12.0f; | ||
| 470 | } | ||
| 471 | |||
| 472 | // Only update state on accelerometer data | ||
| 473 | if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { | ||
| 474 | return; | ||
| 475 | } | ||
| 476 | |||
| 477 | long delta_timestamp = (event.timestamp - motionTimestamp) / 1000; | ||
| 478 | motionTimestamp = event.timestamp; | ||
| 479 | NativeLibrary.onGamePadMotionEvent(NativeLibrary.Player1Device, delta_timestamp, gyro[0], gyro[1], gyro[2], accel[0], accel[1], accel[2]); | ||
| 480 | NativeLibrary.onGamePadMotionEvent(NativeLibrary.ConsoleDevice, delta_timestamp, gyro[0], gyro[1], gyro[2], accel[0], accel[1], accel[2]); | ||
| 481 | } | ||
| 482 | |||
| 483 | @Override | ||
| 484 | public void onAccuracyChanged(Sensor sensor, int i) { | ||
| 485 | } | ||
| 486 | |||
| 487 | private void addOverlayControls(String orientation) { | ||
| 488 | if (mPreferences.getBoolean("buttonToggle0", true)) { | ||
| 489 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_a, | ||
| 490 | R.drawable.facebutton_a_depressed, ButtonType.BUTTON_A, orientation)); | ||
| 491 | } | ||
| 492 | if (mPreferences.getBoolean("buttonToggle1", true)) { | ||
| 493 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_b, | ||
| 494 | R.drawable.facebutton_b_depressed, ButtonType.BUTTON_B, orientation)); | ||
| 495 | } | ||
| 496 | if (mPreferences.getBoolean("buttonToggle2", true)) { | ||
| 497 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_x, | ||
| 498 | R.drawable.facebutton_x_depressed, ButtonType.BUTTON_X, orientation)); | ||
| 499 | } | ||
| 500 | if (mPreferences.getBoolean("buttonToggle3", true)) { | ||
| 501 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_y, | ||
| 502 | R.drawable.facebutton_y_depressed, ButtonType.BUTTON_Y, orientation)); | ||
| 503 | } | ||
| 504 | if (mPreferences.getBoolean("buttonToggle4", true)) { | ||
| 505 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.l_shoulder, | ||
| 506 | R.drawable.l_shoulder_depressed, ButtonType.TRIGGER_L, orientation)); | ||
| 507 | } | ||
| 508 | if (mPreferences.getBoolean("buttonToggle5", true)) { | ||
| 509 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.r_shoulder, | ||
| 510 | R.drawable.r_shoulder_depressed, ButtonType.TRIGGER_R, orientation)); | ||
| 511 | } | ||
| 512 | if (mPreferences.getBoolean("buttonToggle6", true)) { | ||
| 513 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.zl_trigger, | ||
| 514 | R.drawable.zl_trigger_depressed, ButtonType.TRIGGER_ZL, orientation)); | ||
| 515 | } | ||
| 516 | if (mPreferences.getBoolean("buttonToggle7", true)) { | ||
| 517 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.zr_trigger, | ||
| 518 | R.drawable.zr_trigger_depressed, ButtonType.TRIGGER_ZR, orientation)); | ||
| 519 | } | ||
| 520 | if (mPreferences.getBoolean("buttonToggle8", true)) { | ||
| 521 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_plus, | ||
| 522 | R.drawable.facebutton_plus_depressed, ButtonType.BUTTON_PLUS, orientation)); | ||
| 523 | } | ||
| 524 | if (mPreferences.getBoolean("buttonToggle9", true)) { | ||
| 525 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_minus, | ||
| 526 | R.drawable.facebutton_minus_depressed, ButtonType.BUTTON_MINUS, orientation)); | ||
| 527 | } | ||
| 528 | if (mPreferences.getBoolean("buttonToggle10", true)) { | ||
| 529 | overlayDpads.add(initializeOverlayDpad(getContext(), R.drawable.dpad_standard, | ||
| 530 | R.drawable.dpad_standard_cardinal_depressed, | ||
| 531 | R.drawable.dpad_standard_diagonal_depressed, | ||
| 532 | ButtonType.DPAD_UP, ButtonType.DPAD_DOWN, | ||
| 533 | ButtonType.DPAD_LEFT, ButtonType.DPAD_RIGHT, orientation)); | ||
| 534 | } | ||
| 535 | if (mPreferences.getBoolean("buttonToggle11", true)) { | ||
| 536 | overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.joystick_range, | ||
| 537 | R.drawable.joystick, R.drawable.joystick_depressed, | ||
| 538 | StickType.STICK_L, ButtonType.STICK_L, orientation)); | ||
| 539 | } | ||
| 540 | if (mPreferences.getBoolean("buttonToggle12", true)) { | ||
| 541 | overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.joystick_range, | ||
| 542 | R.drawable.joystick, R.drawable.joystick_depressed, StickType.STICK_R, ButtonType.STICK_R, orientation)); | ||
| 543 | } | ||
| 544 | if (mPreferences.getBoolean("buttonToggle13", false)) { | ||
| 545 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_home, | ||
| 546 | R.drawable.facebutton_home_depressed, ButtonType.BUTTON_HOME, orientation)); | ||
| 547 | } | ||
| 548 | if (mPreferences.getBoolean("buttonToggle14", false)) { | ||
| 549 | overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.facebutton_screenshot, | ||
| 550 | R.drawable.facebutton_screenshot_depressed, ButtonType.BUTTON_CAPTURE, orientation)); | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | public void refreshControls() { | ||
| 555 | // Remove all the overlay buttons from the HashSet. | ||
| 556 | overlayButtons.clear(); | ||
| 557 | overlayDpads.clear(); | ||
| 558 | overlayJoysticks.clear(); | ||
| 559 | |||
| 560 | String orientation = | ||
| 561 | getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? | ||
| 562 | "-Portrait" : ""; | ||
| 563 | |||
| 564 | // Add all the enabled overlay items back to the HashSet. | ||
| 565 | if (EmulationMenuSettings.getShowOverlay()) { | ||
| 566 | addOverlayControls(orientation); | ||
| 567 | } | ||
| 568 | |||
| 569 | invalidate(); | ||
| 570 | } | ||
| 571 | |||
| 572 | private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation) { | ||
| 573 | final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); | ||
| 574 | SharedPreferences.Editor sPrefsEditor = sPrefs.edit(); | ||
| 575 | sPrefsEditor.putFloat(sharedPrefsId + orientation + "-X", x); | ||
| 576 | sPrefsEditor.putFloat(sharedPrefsId + orientation + "-Y", y); | ||
| 577 | sPrefsEditor.apply(); | ||
| 578 | } | ||
| 579 | |||
| 580 | public void setIsInEditMode(boolean isInEditMode) { | ||
| 581 | mIsInEditMode = isInEditMode; | ||
| 582 | } | ||
| 583 | |||
| 584 | private void defaultOverlay() { | ||
| 585 | if (!mPreferences.getBoolean("OverlayInit", false)) { | ||
| 586 | defaultOverlayLandscape(); | ||
| 587 | } | ||
| 588 | resetButtonPlacement(); | ||
| 589 | SharedPreferences.Editor sPrefsEditor = mPreferences.edit(); | ||
| 590 | sPrefsEditor.putBoolean("OverlayInit", true); | ||
| 591 | sPrefsEditor.apply(); | ||
| 592 | } | ||
| 593 | |||
| 594 | public void resetButtonPlacement() { | ||
| 595 | defaultOverlayLandscape(); | ||
| 596 | refreshControls(); | ||
| 597 | } | ||
| 598 | |||
| 599 | private void defaultOverlayLandscape() { | ||
| 600 | SharedPreferences.Editor sPrefsEditor = mPreferences.edit(); | ||
| 601 | // Get screen size | ||
| 602 | Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay(); | ||
| 603 | DisplayMetrics outMetrics = new DisplayMetrics(); | ||
| 604 | display.getRealMetrics(outMetrics); | ||
| 605 | float maxX = outMetrics.heightPixels; | ||
| 606 | float maxY = outMetrics.widthPixels; | ||
| 607 | // Height and width changes depending on orientation. Use the larger value for height. | ||
| 608 | if (maxY > maxX) { | ||
| 609 | float tmp = maxX; | ||
| 610 | maxX = maxY; | ||
| 611 | maxY = tmp; | ||
| 612 | } | ||
| 613 | |||
| 614 | Resources res = getResources(); | ||
| 615 | |||
| 616 | // Each value is a percent from max X/Y stored as an int. Have to bring that value down | ||
| 617 | // to a decimal before multiplying by MAX X/Y. | ||
| 618 | sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_A_X) / 1000) * maxX)); | ||
| 619 | sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_A_Y) / 1000) * maxY)); | ||
| 620 | sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_B_X) / 1000) * maxX)); | ||
| 621 | sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_B_Y) / 1000) * maxY)); | ||
| 622 | sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_X_X) / 1000) * maxX)); | ||
| 623 | sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_X_Y) / 1000) * maxY)); | ||
| 624 | sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_Y_X) / 1000) * maxX)); | ||
| 625 | sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_Y_Y) / 1000) * maxY)); | ||
| 626 | sPrefsEditor.putFloat(ButtonType.TRIGGER_ZL + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZL_X) / 1000) * maxX)); | ||
| 627 | sPrefsEditor.putFloat(ButtonType.TRIGGER_ZL + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y) / 1000) * maxY)); | ||
| 628 | sPrefsEditor.putFloat(ButtonType.TRIGGER_ZR + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZR_X) / 1000) * maxX)); | ||
| 629 | sPrefsEditor.putFloat(ButtonType.TRIGGER_ZR + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y) / 1000) * maxY)); | ||
| 630 | sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_DPAD_X) / 1000) * maxX)); | ||
| 631 | sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y) / 1000) * maxY)); | ||
| 632 | sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_L_X) / 1000) * maxX)); | ||
| 633 | sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_L_Y) / 1000) * maxY)); | ||
| 634 | sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-X", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_R_X) / 1000) * maxX)); | ||
| 635 | sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-Y", (((float) res.getInteger(R.integer.SWITCH_TRIGGER_R_Y) / 1000) * maxY)); | ||
| 636 | sPrefsEditor.putFloat(ButtonType.BUTTON_PLUS + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_PLUS_X) / 1000) * maxX)); | ||
| 637 | sPrefsEditor.putFloat(ButtonType.BUTTON_PLUS + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y) / 1000) * maxY)); | ||
| 638 | sPrefsEditor.putFloat(ButtonType.BUTTON_MINUS + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_MINUS_X) / 1000) * maxX)); | ||
| 639 | sPrefsEditor.putFloat(ButtonType.BUTTON_MINUS + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y) / 1000) * maxY)); | ||
| 640 | sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_HOME_X) / 1000) * maxX)); | ||
| 641 | sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_HOME_Y) / 1000) * maxY)); | ||
| 642 | sPrefsEditor.putFloat(ButtonType.BUTTON_CAPTURE + "-X", (((float) res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X) / 1000) * maxX)); | ||
| 643 | sPrefsEditor.putFloat(ButtonType.BUTTON_CAPTURE + "-Y", (((float) res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y) / 1000) * maxY)); | ||
| 644 | sPrefsEditor.putFloat(ButtonType.STICK_R + "-X", (((float) res.getInteger(R.integer.SWITCH_STICK_R_X) / 1000) * maxX)); | ||
| 645 | sPrefsEditor.putFloat(ButtonType.STICK_R + "-Y", (((float) res.getInteger(R.integer.SWITCH_STICK_R_Y) / 1000) * maxY)); | ||
| 646 | sPrefsEditor.putFloat(ButtonType.STICK_L + "-X", (((float) res.getInteger(R.integer.SWITCH_STICK_L_X) / 1000) * maxX)); | ||
| 647 | sPrefsEditor.putFloat(ButtonType.STICK_L + "-Y", (((float) res.getInteger(R.integer.SWITCH_STICK_L_Y) / 1000) * maxY)); | ||
| 648 | |||
| 649 | // We want to commit right away, otherwise the overlay could load before this is saved. | ||
| 650 | sPrefsEditor.commit(); | ||
| 651 | } | ||
| 652 | |||
| 653 | public boolean isInEditMode() { | ||
| 654 | return mIsInEditMode; | ||
| 655 | } | ||
| 656 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt new file mode 100644 index 000000000..a964b6257 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | |||
| @@ -0,0 +1,886 @@ | |||
| 1 | package org.yuzu.yuzu_emu.overlay | ||
| 2 | |||
| 3 | import android.app.Activity | ||
| 4 | import android.content.Context | ||
| 5 | import android.content.SharedPreferences | ||
| 6 | import android.content.res.Configuration | ||
| 7 | import android.graphics.Bitmap | ||
| 8 | import android.graphics.BitmapFactory | ||
| 9 | import android.graphics.Canvas | ||
| 10 | import android.graphics.Rect | ||
| 11 | import android.graphics.drawable.BitmapDrawable | ||
| 12 | import android.graphics.drawable.Drawable | ||
| 13 | import android.graphics.drawable.VectorDrawable | ||
| 14 | import android.hardware.Sensor | ||
| 15 | import android.hardware.SensorEvent | ||
| 16 | import android.hardware.SensorEventListener | ||
| 17 | import android.hardware.SensorManager | ||
| 18 | import android.util.AttributeSet | ||
| 19 | import android.util.DisplayMetrics | ||
| 20 | import android.view.MotionEvent | ||
| 21 | import android.view.SurfaceView | ||
| 22 | import android.view.View | ||
| 23 | import android.view.View.OnTouchListener | ||
| 24 | import androidx.core.content.ContextCompat | ||
| 25 | import androidx.preference.PreferenceManager | ||
| 26 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 27 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType | ||
| 28 | import org.yuzu.yuzu_emu.NativeLibrary.StickType | ||
| 29 | import org.yuzu.yuzu_emu.R | ||
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 31 | import org.yuzu.yuzu_emu.features.settings.model.Settings | ||
| 32 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 33 | |||
| 34 | |||
| 35 | /** | ||
| 36 | * Draws the interactive input overlay on top of the | ||
| 37 | * [SurfaceView] that is rendering emulation. | ||
| 38 | */ | ||
| 39 | class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), | ||
| 40 | OnTouchListener, SensorEventListener { | ||
| 41 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() | ||
| 42 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() | ||
| 43 | private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet() | ||
| 44 | private var inEditMode = false | ||
| 45 | private val preferences: SharedPreferences = | ||
| 46 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 47 | private val gyro = FloatArray(3) | ||
| 48 | private val accel = FloatArray(3) | ||
| 49 | private var motionTimestamp: Long = 0 | ||
| 50 | |||
| 51 | init { | ||
| 52 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | ||
| 53 | defaultOverlay() | ||
| 54 | } | ||
| 55 | |||
| 56 | // Load the controls. | ||
| 57 | refreshControls() | ||
| 58 | |||
| 59 | // Set the on motion sensor listener. | ||
| 60 | setMotionSensorListener(context) | ||
| 61 | |||
| 62 | // Set the on touch listener. | ||
| 63 | setOnTouchListener(this) | ||
| 64 | |||
| 65 | // Force draw | ||
| 66 | setWillNotDraw(false) | ||
| 67 | |||
| 68 | // Request focus for the overlay so it has priority on presses. | ||
| 69 | requestFocus() | ||
| 70 | } | ||
| 71 | |||
| 72 | private fun setMotionSensorListener(context: Context) { | ||
| 73 | val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager | ||
| 74 | val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) | ||
| 75 | val accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) | ||
| 76 | if (gyroSensor != null) { | ||
| 77 | sensorManager.registerListener(this, gyroSensor, SensorManager.SENSOR_DELAY_GAME) | ||
| 78 | } | ||
| 79 | if (accelSensor != null) { | ||
| 80 | sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | override fun draw(canvas: Canvas) { | ||
| 85 | super.draw(canvas) | ||
| 86 | for (button in overlayButtons) { | ||
| 87 | button.draw(canvas) | ||
| 88 | } | ||
| 89 | for (dpad in overlayDpads) { | ||
| 90 | dpad.draw(canvas) | ||
| 91 | } | ||
| 92 | for (joystick in overlayJoysticks) { | ||
| 93 | joystick.draw(canvas) | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | override fun onTouch(v: View, event: MotionEvent): Boolean { | ||
| 98 | if (inEditMode) { | ||
| 99 | return onTouchWhileEditing(event) | ||
| 100 | } | ||
| 101 | |||
| 102 | var shouldUpdateView = false | ||
| 103 | |||
| 104 | for (button in overlayButtons) { | ||
| 105 | if (!button.updateStatus(event)) { | ||
| 106 | continue | ||
| 107 | } | ||
| 108 | NativeLibrary.onGamePadButtonEvent( | ||
| 109 | NativeLibrary.Player1Device, | ||
| 110 | button.id, | ||
| 111 | button.status | ||
| 112 | ) | ||
| 113 | shouldUpdateView = true | ||
| 114 | } | ||
| 115 | |||
| 116 | for (dpad in overlayDpads) { | ||
| 117 | if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlideEnable)) { | ||
| 118 | continue | ||
| 119 | } | ||
| 120 | NativeLibrary.onGamePadButtonEvent( | ||
| 121 | NativeLibrary.Player1Device, | ||
| 122 | dpad.upId, | ||
| 123 | dpad.upStatus | ||
| 124 | ) | ||
| 125 | NativeLibrary.onGamePadButtonEvent( | ||
| 126 | NativeLibrary.Player1Device, | ||
| 127 | dpad.downId, | ||
| 128 | dpad.downStatus | ||
| 129 | ) | ||
| 130 | NativeLibrary.onGamePadButtonEvent( | ||
| 131 | NativeLibrary.Player1Device, | ||
| 132 | dpad.leftId, | ||
| 133 | dpad.leftStatus | ||
| 134 | ) | ||
| 135 | NativeLibrary.onGamePadButtonEvent( | ||
| 136 | NativeLibrary.Player1Device, | ||
| 137 | dpad.rightId, | ||
| 138 | dpad.rightStatus | ||
| 139 | ) | ||
| 140 | shouldUpdateView = true | ||
| 141 | } | ||
| 142 | |||
| 143 | for (joystick in overlayJoysticks) { | ||
| 144 | if (!joystick.updateStatus(event)) { | ||
| 145 | continue | ||
| 146 | } | ||
| 147 | val axisID = joystick.joystickId | ||
| 148 | NativeLibrary.onGamePadJoystickEvent( | ||
| 149 | NativeLibrary.Player1Device, | ||
| 150 | axisID, | ||
| 151 | joystick.xAxis, | ||
| 152 | joystick.realYAxis | ||
| 153 | ) | ||
| 154 | NativeLibrary.onGamePadButtonEvent( | ||
| 155 | NativeLibrary.Player1Device, | ||
| 156 | joystick.buttonId, | ||
| 157 | joystick.buttonStatus | ||
| 158 | ) | ||
| 159 | shouldUpdateView = true | ||
| 160 | } | ||
| 161 | |||
| 162 | if (shouldUpdateView) | ||
| 163 | invalidate() | ||
| 164 | |||
| 165 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { | ||
| 166 | return true | ||
| 167 | } | ||
| 168 | |||
| 169 | val pointerIndex = event.actionIndex | ||
| 170 | val xPosition = event.getX(pointerIndex).toInt() | ||
| 171 | val yPosition = event.getY(pointerIndex).toInt() | ||
| 172 | val pointerId = event.getPointerId(pointerIndex) | ||
| 173 | val motionEvent = event.action and MotionEvent.ACTION_MASK | ||
| 174 | val isActionDown = | ||
| 175 | motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN | ||
| 176 | val isActionMove = motionEvent == MotionEvent.ACTION_MOVE | ||
| 177 | val isActionUp = | ||
| 178 | motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP | ||
| 179 | |||
| 180 | if (isActionDown && !isTouchInputConsumed(pointerId)) { | ||
| 181 | NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat()) | ||
| 182 | } | ||
| 183 | |||
| 184 | if (isActionMove) { | ||
| 185 | for (i in 0 until event.pointerCount) { | ||
| 186 | val fingerId = event.getPointerId(i) | ||
| 187 | if (isTouchInputConsumed(fingerId)) { | ||
| 188 | continue | ||
| 189 | } | ||
| 190 | NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i)) | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | if (isActionUp && !isTouchInputConsumed(pointerId)) { | ||
| 195 | NativeLibrary.onTouchReleased(pointerId) | ||
| 196 | } | ||
| 197 | |||
| 198 | return true | ||
| 199 | } | ||
| 200 | |||
| 201 | private fun isTouchInputConsumed(track_id: Int): Boolean { | ||
| 202 | for (button in overlayButtons) { | ||
| 203 | if (button.trackId == track_id) { | ||
| 204 | return true | ||
| 205 | } | ||
| 206 | } | ||
| 207 | for (dpad in overlayDpads) { | ||
| 208 | if (dpad.trackId == track_id) { | ||
| 209 | return true | ||
| 210 | } | ||
| 211 | } | ||
| 212 | for (joystick in overlayJoysticks) { | ||
| 213 | if (joystick.trackId == track_id) { | ||
| 214 | return true | ||
| 215 | } | ||
| 216 | } | ||
| 217 | return false | ||
| 218 | } | ||
| 219 | |||
| 220 | private fun onTouchWhileEditing(event: MotionEvent?): Boolean { | ||
| 221 | // TODO: Reimplement this | ||
| 222 | return true | ||
| 223 | } | ||
| 224 | |||
| 225 | override fun onSensorChanged(event: SensorEvent) { | ||
| 226 | if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { | ||
| 227 | accel[0] = -event.values[1] / SensorManager.GRAVITY_EARTH | ||
| 228 | accel[1] = event.values[0] / SensorManager.GRAVITY_EARTH | ||
| 229 | accel[2] = -event.values[2] / SensorManager.GRAVITY_EARTH | ||
| 230 | } | ||
| 231 | if (event.sensor.type == Sensor.TYPE_GYROSCOPE) { | ||
| 232 | // Investigate why sensor value is off by 12x | ||
| 233 | gyro[0] = event.values[1] / 12.0f | ||
| 234 | gyro[1] = -event.values[0] / 12.0f | ||
| 235 | gyro[2] = event.values[2] / 12.0f | ||
| 236 | } | ||
| 237 | |||
| 238 | // Only update state on accelerometer data | ||
| 239 | if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) { | ||
| 240 | return | ||
| 241 | } | ||
| 242 | val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000 | ||
| 243 | motionTimestamp = event.timestamp | ||
| 244 | NativeLibrary.onGamePadMotionEvent( | ||
| 245 | NativeLibrary.Player1Device, | ||
| 246 | deltaTimestamp, | ||
| 247 | gyro[0], | ||
| 248 | gyro[1], | ||
| 249 | gyro[2], | ||
| 250 | accel[0], | ||
| 251 | accel[1], | ||
| 252 | accel[2] | ||
| 253 | ) | ||
| 254 | NativeLibrary.onGamePadMotionEvent( | ||
| 255 | NativeLibrary.ConsoleDevice, | ||
| 256 | deltaTimestamp, | ||
| 257 | gyro[0], | ||
| 258 | gyro[1], | ||
| 259 | gyro[2], | ||
| 260 | accel[0], | ||
| 261 | accel[1], | ||
| 262 | accel[2] | ||
| 263 | ) | ||
| 264 | } | ||
| 265 | |||
| 266 | override fun onAccuracyChanged(sensor: Sensor, i: Int) {} | ||
| 267 | private fun addOverlayControls(orientation: String) { | ||
| 268 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) { | ||
| 269 | overlayButtons.add( | ||
| 270 | initializeOverlayButton( | ||
| 271 | context, | ||
| 272 | R.drawable.facebutton_a, | ||
| 273 | R.drawable.facebutton_a_depressed, | ||
| 274 | ButtonType.BUTTON_A, | ||
| 275 | orientation | ||
| 276 | ) | ||
| 277 | ) | ||
| 278 | } | ||
| 279 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) { | ||
| 280 | overlayButtons.add( | ||
| 281 | initializeOverlayButton( | ||
| 282 | context, | ||
| 283 | R.drawable.facebutton_b, | ||
| 284 | R.drawable.facebutton_b_depressed, | ||
| 285 | ButtonType.BUTTON_B, | ||
| 286 | orientation | ||
| 287 | ) | ||
| 288 | ) | ||
| 289 | } | ||
| 290 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) { | ||
| 291 | overlayButtons.add( | ||
| 292 | initializeOverlayButton( | ||
| 293 | context, | ||
| 294 | R.drawable.facebutton_x, | ||
| 295 | R.drawable.facebutton_x_depressed, | ||
| 296 | ButtonType.BUTTON_X, | ||
| 297 | orientation | ||
| 298 | ) | ||
| 299 | ) | ||
| 300 | } | ||
| 301 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) { | ||
| 302 | overlayButtons.add( | ||
| 303 | initializeOverlayButton( | ||
| 304 | context, | ||
| 305 | R.drawable.facebutton_y, | ||
| 306 | R.drawable.facebutton_y_depressed, | ||
| 307 | ButtonType.BUTTON_Y, | ||
| 308 | orientation | ||
| 309 | ) | ||
| 310 | ) | ||
| 311 | } | ||
| 312 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) { | ||
| 313 | overlayButtons.add( | ||
| 314 | initializeOverlayButton( | ||
| 315 | context, | ||
| 316 | R.drawable.l_shoulder, | ||
| 317 | R.drawable.l_shoulder_depressed, | ||
| 318 | ButtonType.TRIGGER_L, | ||
| 319 | orientation | ||
| 320 | ) | ||
| 321 | ) | ||
| 322 | } | ||
| 323 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) { | ||
| 324 | overlayButtons.add( | ||
| 325 | initializeOverlayButton( | ||
| 326 | context, | ||
| 327 | R.drawable.r_shoulder, | ||
| 328 | R.drawable.r_shoulder_depressed, | ||
| 329 | ButtonType.TRIGGER_R, | ||
| 330 | orientation | ||
| 331 | ) | ||
| 332 | ) | ||
| 333 | } | ||
| 334 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) { | ||
| 335 | overlayButtons.add( | ||
| 336 | initializeOverlayButton( | ||
| 337 | context, | ||
| 338 | R.drawable.zl_trigger, | ||
| 339 | R.drawable.zl_trigger_depressed, | ||
| 340 | ButtonType.TRIGGER_ZL, | ||
| 341 | orientation | ||
| 342 | ) | ||
| 343 | ) | ||
| 344 | } | ||
| 345 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) { | ||
| 346 | overlayButtons.add( | ||
| 347 | initializeOverlayButton( | ||
| 348 | context, | ||
| 349 | R.drawable.zr_trigger, | ||
| 350 | R.drawable.zr_trigger_depressed, | ||
| 351 | ButtonType.TRIGGER_ZR, | ||
| 352 | orientation | ||
| 353 | ) | ||
| 354 | ) | ||
| 355 | } | ||
| 356 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) { | ||
| 357 | overlayButtons.add( | ||
| 358 | initializeOverlayButton( | ||
| 359 | context, | ||
| 360 | R.drawable.facebutton_plus, | ||
| 361 | R.drawable.facebutton_plus_depressed, | ||
| 362 | ButtonType.BUTTON_PLUS, | ||
| 363 | orientation | ||
| 364 | ) | ||
| 365 | ) | ||
| 366 | } | ||
| 367 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) { | ||
| 368 | overlayButtons.add( | ||
| 369 | initializeOverlayButton( | ||
| 370 | context, | ||
| 371 | R.drawable.facebutton_minus, | ||
| 372 | R.drawable.facebutton_minus_depressed, | ||
| 373 | ButtonType.BUTTON_MINUS, | ||
| 374 | orientation | ||
| 375 | ) | ||
| 376 | ) | ||
| 377 | } | ||
| 378 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) { | ||
| 379 | overlayDpads.add( | ||
| 380 | initializeOverlayDpad( | ||
| 381 | context, | ||
| 382 | R.drawable.dpad_standard, | ||
| 383 | R.drawable.dpad_standard_cardinal_depressed, | ||
| 384 | R.drawable.dpad_standard_diagonal_depressed, | ||
| 385 | orientation | ||
| 386 | ) | ||
| 387 | ) | ||
| 388 | } | ||
| 389 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) { | ||
| 390 | overlayJoysticks.add( | ||
| 391 | initializeOverlayJoystick( | ||
| 392 | context, | ||
| 393 | R.drawable.joystick_range, | ||
| 394 | R.drawable.joystick, | ||
| 395 | R.drawable.joystick_depressed, | ||
| 396 | StickType.STICK_L, | ||
| 397 | ButtonType.STICK_L, | ||
| 398 | orientation | ||
| 399 | ) | ||
| 400 | ) | ||
| 401 | } | ||
| 402 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) { | ||
| 403 | overlayJoysticks.add( | ||
| 404 | initializeOverlayJoystick( | ||
| 405 | context, | ||
| 406 | R.drawable.joystick_range, | ||
| 407 | R.drawable.joystick, | ||
| 408 | R.drawable.joystick_depressed, | ||
| 409 | StickType.STICK_R, | ||
| 410 | ButtonType.STICK_R, | ||
| 411 | orientation | ||
| 412 | ) | ||
| 413 | ) | ||
| 414 | } | ||
| 415 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) { | ||
| 416 | overlayButtons.add( | ||
| 417 | initializeOverlayButton( | ||
| 418 | context, | ||
| 419 | R.drawable.facebutton_home, | ||
| 420 | R.drawable.facebutton_home_depressed, | ||
| 421 | ButtonType.BUTTON_HOME, | ||
| 422 | orientation | ||
| 423 | ) | ||
| 424 | ) | ||
| 425 | } | ||
| 426 | if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) { | ||
| 427 | overlayButtons.add( | ||
| 428 | initializeOverlayButton( | ||
| 429 | context, | ||
| 430 | R.drawable.facebutton_screenshot, | ||
| 431 | R.drawable.facebutton_screenshot_depressed, | ||
| 432 | ButtonType.BUTTON_CAPTURE, | ||
| 433 | orientation | ||
| 434 | ) | ||
| 435 | ) | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | fun refreshControls() { | ||
| 440 | // Remove all the overlay buttons from the HashSet. | ||
| 441 | overlayButtons.clear() | ||
| 442 | overlayDpads.clear() | ||
| 443 | overlayJoysticks.clear() | ||
| 444 | val orientation = | ||
| 445 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else "" | ||
| 446 | |||
| 447 | // Add all the enabled overlay items back to the HashSet. | ||
| 448 | if (EmulationMenuSettings.showOverlay) { | ||
| 449 | addOverlayControls(orientation) | ||
| 450 | } | ||
| 451 | invalidate() | ||
| 452 | } | ||
| 453 | |||
| 454 | private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) { | ||
| 455 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | ||
| 456 | .putFloat("$sharedPrefsId$orientation-X", x.toFloat()) | ||
| 457 | .putFloat("$sharedPrefsId$orientation-Y", y.toFloat()) | ||
| 458 | .apply() | ||
| 459 | } | ||
| 460 | |||
| 461 | fun setIsInEditMode(editMode: Boolean) { | ||
| 462 | inEditMode = editMode | ||
| 463 | } | ||
| 464 | |||
| 465 | private fun defaultOverlay() { | ||
| 466 | if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) { | ||
| 467 | defaultOverlayLandscape() | ||
| 468 | } | ||
| 469 | |||
| 470 | resetButtonPlacement() | ||
| 471 | preferences.edit() | ||
| 472 | .putBoolean(Settings.PREF_OVERLAY_INIT, true) | ||
| 473 | .apply() | ||
| 474 | } | ||
| 475 | |||
| 476 | fun resetButtonPlacement() { | ||
| 477 | defaultOverlayLandscape() | ||
| 478 | refreshControls() | ||
| 479 | } | ||
| 480 | |||
| 481 | private fun defaultOverlayLandscape() { | ||
| 482 | // Get screen size | ||
| 483 | val display = (context as Activity).windowManager.defaultDisplay | ||
| 484 | val outMetrics = DisplayMetrics() | ||
| 485 | display.getRealMetrics(outMetrics) | ||
| 486 | var maxX = outMetrics.heightPixels.toFloat() | ||
| 487 | var maxY = outMetrics.widthPixels.toFloat() | ||
| 488 | // Height and width changes depending on orientation. Use the larger value for height. | ||
| 489 | if (maxY > maxX) { | ||
| 490 | val tmp = maxX | ||
| 491 | maxX = maxY | ||
| 492 | maxY = tmp | ||
| 493 | } | ||
| 494 | val res = resources | ||
| 495 | |||
| 496 | // Each value is a percent from max X/Y stored as an int. Have to bring that value down | ||
| 497 | // to a decimal before multiplying by MAX X/Y. | ||
| 498 | preferences.edit() | ||
| 499 | .putFloat( | ||
| 500 | ButtonType.BUTTON_A.toString() + "-X", | ||
| 501 | res.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 * maxX | ||
| 502 | ) | ||
| 503 | .putFloat( | ||
| 504 | ButtonType.BUTTON_A.toString() + "-Y", | ||
| 505 | res.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 * maxY | ||
| 506 | ) | ||
| 507 | .putFloat( | ||
| 508 | ButtonType.BUTTON_B.toString() + "-X", | ||
| 509 | res.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 * maxX | ||
| 510 | ) | ||
| 511 | .putFloat( | ||
| 512 | ButtonType.BUTTON_B.toString() + "-Y", | ||
| 513 | res.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 * maxY | ||
| 514 | ) | ||
| 515 | .putFloat( | ||
| 516 | ButtonType.BUTTON_X.toString() + "-X", | ||
| 517 | res.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 * maxX | ||
| 518 | ) | ||
| 519 | .putFloat( | ||
| 520 | ButtonType.BUTTON_X.toString() + "-Y", | ||
| 521 | res.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 * maxY | ||
| 522 | ) | ||
| 523 | .putFloat( | ||
| 524 | ButtonType.BUTTON_Y.toString() + "-X", | ||
| 525 | res.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 * maxX | ||
| 526 | ) | ||
| 527 | .putFloat( | ||
| 528 | ButtonType.BUTTON_Y.toString() + "-Y", | ||
| 529 | res.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 * maxY | ||
| 530 | ) | ||
| 531 | .putFloat( | ||
| 532 | ButtonType.TRIGGER_ZL.toString() + "-X", | ||
| 533 | res.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 * maxX | ||
| 534 | ) | ||
| 535 | .putFloat( | ||
| 536 | ButtonType.TRIGGER_ZL.toString() + "-Y", | ||
| 537 | res.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 * maxY | ||
| 538 | ) | ||
| 539 | .putFloat( | ||
| 540 | ButtonType.TRIGGER_ZR.toString() + "-X", | ||
| 541 | res.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 * maxX | ||
| 542 | ) | ||
| 543 | .putFloat( | ||
| 544 | ButtonType.TRIGGER_ZR.toString() + "-Y", | ||
| 545 | res.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 * maxY | ||
| 546 | ) | ||
| 547 | .putFloat( | ||
| 548 | ButtonType.DPAD_UP.toString() + "-X", | ||
| 549 | res.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 * maxX | ||
| 550 | ) | ||
| 551 | .putFloat( | ||
| 552 | ButtonType.DPAD_UP.toString() + "-Y", | ||
| 553 | res.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 * maxY | ||
| 554 | ) | ||
| 555 | .putFloat( | ||
| 556 | ButtonType.TRIGGER_L.toString() + "-X", | ||
| 557 | res.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 * maxX | ||
| 558 | ) | ||
| 559 | .putFloat( | ||
| 560 | ButtonType.TRIGGER_L.toString() + "-Y", | ||
| 561 | res.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 * maxY | ||
| 562 | ) | ||
| 563 | .putFloat( | ||
| 564 | ButtonType.TRIGGER_R.toString() + "-X", | ||
| 565 | res.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 * maxX | ||
| 566 | ) | ||
| 567 | .putFloat( | ||
| 568 | ButtonType.TRIGGER_R.toString() + "-Y", | ||
| 569 | res.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 * maxY | ||
| 570 | ) | ||
| 571 | .putFloat( | ||
| 572 | ButtonType.BUTTON_PLUS.toString() + "-X", | ||
| 573 | res.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 * maxX | ||
| 574 | ) | ||
| 575 | .putFloat( | ||
| 576 | ButtonType.BUTTON_PLUS.toString() + "-Y", | ||
| 577 | res.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 * maxY | ||
| 578 | ) | ||
| 579 | .putFloat( | ||
| 580 | ButtonType.BUTTON_MINUS.toString() + "-X", | ||
| 581 | res.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 * maxX | ||
| 582 | ) | ||
| 583 | .putFloat( | ||
| 584 | ButtonType.BUTTON_MINUS.toString() + "-Y", | ||
| 585 | res.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 * maxY | ||
| 586 | ) | ||
| 587 | .putFloat( | ||
| 588 | ButtonType.BUTTON_HOME.toString() + "-X", | ||
| 589 | res.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 * maxX | ||
| 590 | ) | ||
| 591 | .putFloat( | ||
| 592 | ButtonType.BUTTON_HOME.toString() + "-Y", | ||
| 593 | res.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 * maxY | ||
| 594 | ) | ||
| 595 | .putFloat( | ||
| 596 | ButtonType.BUTTON_CAPTURE.toString() + "-X", | ||
| 597 | res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X).toFloat() / 1000 * maxX | ||
| 598 | ) | ||
| 599 | .putFloat( | ||
| 600 | ButtonType.BUTTON_CAPTURE.toString() + "-Y", | ||
| 601 | res.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y).toFloat() / 1000 * maxY | ||
| 602 | ) | ||
| 603 | .putFloat( | ||
| 604 | ButtonType.STICK_R.toString() + "-X", | ||
| 605 | res.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 * maxX | ||
| 606 | ) | ||
| 607 | .putFloat( | ||
| 608 | ButtonType.STICK_R.toString() + "-Y", | ||
| 609 | res.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 * maxY | ||
| 610 | ) | ||
| 611 | .putFloat( | ||
| 612 | ButtonType.STICK_L.toString() + "-X", | ||
| 613 | res.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 * maxX | ||
| 614 | ) | ||
| 615 | .putFloat( | ||
| 616 | ButtonType.STICK_L.toString() + "-Y", | ||
| 617 | res.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 * maxY | ||
| 618 | ) | ||
| 619 | .commit() | ||
| 620 | // We want to commit right away, otherwise the overlay could load before this is saved. | ||
| 621 | } | ||
| 622 | |||
| 623 | override fun isInEditMode(): Boolean { | ||
| 624 | return inEditMode | ||
| 625 | } | ||
| 626 | |||
| 627 | companion object { | ||
| 628 | /** | ||
| 629 | * Resizes a [Bitmap] by a given scale factor | ||
| 630 | * | ||
| 631 | * @param vectorDrawable The {@link Bitmap} to scale. | ||
| 632 | * @param scale The scale factor for the bitmap. | ||
| 633 | * @return The scaled [Bitmap] | ||
| 634 | */ | ||
| 635 | private fun getBitmap(vectorDrawable: VectorDrawable, scale: Float): Bitmap { | ||
| 636 | val bitmap = Bitmap.createBitmap( | ||
| 637 | (vectorDrawable.intrinsicWidth * scale).toInt(), | ||
| 638 | (vectorDrawable.intrinsicHeight * scale).toInt(), | ||
| 639 | Bitmap.Config.ARGB_8888 | ||
| 640 | ) | ||
| 641 | val canvas = Canvas(bitmap) | ||
| 642 | vectorDrawable.setBounds(0, 0, canvas.width, canvas.height) | ||
| 643 | vectorDrawable.draw(canvas) | ||
| 644 | return bitmap | ||
| 645 | } | ||
| 646 | |||
| 647 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { | ||
| 648 | return when (val drawable = ContextCompat.getDrawable(context, drawableId)) { | ||
| 649 | is BitmapDrawable -> BitmapFactory.decodeResource(context.resources, drawableId) | ||
| 650 | is VectorDrawable -> getBitmap(drawable, scale) | ||
| 651 | else -> throw IllegalArgumentException("Unsupported drawable type") | ||
| 652 | } | ||
| 653 | } | ||
| 654 | |||
| 655 | /** | ||
| 656 | * Initializes an InputOverlayDrawableButton, given by resId, with all of the | ||
| 657 | * parameters set for it to be properly shown on the InputOverlay. | ||
| 658 | * | ||
| 659 | * | ||
| 660 | * This works due to the way the X and Y coordinates are stored within | ||
| 661 | * the [SharedPreferences]. | ||
| 662 | * | ||
| 663 | * | ||
| 664 | * In the input overlay configuration menu, | ||
| 665 | * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay). | ||
| 666 | * the X and Y coordinates of the button at the END of its touch event | ||
| 667 | * (when you remove your finger/stylus from the touchscreen) are then stored | ||
| 668 | * within a SharedPreferences instance so that those values can be retrieved here. | ||
| 669 | * | ||
| 670 | * | ||
| 671 | * This has a few benefits over the conventional way of storing the values | ||
| 672 | * (ie. within the yuzu ini file). | ||
| 673 | * | ||
| 674 | * * No native calls | ||
| 675 | * * Keeps Android-only values inside the Android environment | ||
| 676 | * | ||
| 677 | * | ||
| 678 | * | ||
| 679 | * Technically no modifications should need to be performed on the returned | ||
| 680 | * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait | ||
| 681 | * for Android to call the onDraw method. | ||
| 682 | * | ||
| 683 | * @param context The current [Context]. | ||
| 684 | * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). | ||
| 685 | * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). | ||
| 686 | * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. | ||
| 687 | * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. | ||
| 688 | */ | ||
| 689 | private fun initializeOverlayButton( | ||
| 690 | context: Context, | ||
| 691 | defaultResId: Int, | ||
| 692 | pressedResId: Int, | ||
| 693 | buttonId: Int, | ||
| 694 | orientation: String | ||
| 695 | ): InputOverlayDrawableButton { | ||
| 696 | // Resources handle for fetching the initial Drawable resource. | ||
| 697 | val res = context.resources | ||
| 698 | |||
| 699 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. | ||
| 700 | val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 701 | |||
| 702 | // Decide scale based on button ID and user preference | ||
| 703 | var scale: Float = when (buttonId) { | ||
| 704 | ButtonType.BUTTON_HOME, | ||
| 705 | ButtonType.BUTTON_CAPTURE, | ||
| 706 | ButtonType.BUTTON_PLUS, | ||
| 707 | ButtonType.BUTTON_MINUS -> 0.35f | ||
| 708 | ButtonType.TRIGGER_L, | ||
| 709 | ButtonType.TRIGGER_R, | ||
| 710 | ButtonType.TRIGGER_ZL, | ||
| 711 | ButtonType.TRIGGER_ZR -> 0.38f | ||
| 712 | else -> 0.43f | ||
| 713 | } | ||
| 714 | scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() | ||
| 715 | scale /= 100f | ||
| 716 | |||
| 717 | // Initialize the InputOverlayDrawableButton. | ||
| 718 | val defaultStateBitmap = getBitmap(context, defaultResId, scale) | ||
| 719 | val pressedStateBitmap = getBitmap(context, pressedResId, scale) | ||
| 720 | val overlayDrawable = | ||
| 721 | InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId) | ||
| 722 | |||
| 723 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | ||
| 724 | // These were set in the input overlay configuration menu. | ||
| 725 | val xKey = "$buttonId$orientation-X" | ||
| 726 | val yKey = "$buttonId$orientation-Y" | ||
| 727 | val drawableX = sPrefs.getFloat(xKey, 0f).toInt() | ||
| 728 | val drawableY = sPrefs.getFloat(yKey, 0f).toInt() | ||
| 729 | val width = overlayDrawable.width | ||
| 730 | val height = overlayDrawable.height | ||
| 731 | |||
| 732 | // Now set the bounds for the InputOverlayDrawableButton. | ||
| 733 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be. | ||
| 734 | overlayDrawable.setBounds( | ||
| 735 | drawableX - (width / 2), | ||
| 736 | drawableY - (height / 2), | ||
| 737 | drawableX + (width / 2), | ||
| 738 | drawableY + (height / 2) | ||
| 739 | ) | ||
| 740 | |||
| 741 | // Need to set the image's position | ||
| 742 | overlayDrawable.setPosition( | ||
| 743 | drawableX - (width / 2), | ||
| 744 | drawableY - (height / 2) | ||
| 745 | ) | ||
| 746 | return overlayDrawable | ||
| 747 | } | ||
| 748 | |||
| 749 | /** | ||
| 750 | * Initializes an [InputOverlayDrawableDpad] | ||
| 751 | * | ||
| 752 | * @param context The current [Context]. | ||
| 753 | * @param defaultResId The [Bitmap] resource ID of the default sate. | ||
| 754 | * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed sate in one direction. | ||
| 755 | * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed sate in two directions. | ||
| 756 | * @return the initialized [InputOverlayDrawableDpad] | ||
| 757 | */ | ||
| 758 | private fun initializeOverlayDpad( | ||
| 759 | context: Context, | ||
| 760 | defaultResId: Int, | ||
| 761 | pressedOneDirectionResId: Int, | ||
| 762 | pressedTwoDirectionsResId: Int, | ||
| 763 | orientation: String | ||
| 764 | ): InputOverlayDrawableDpad { | ||
| 765 | // Resources handle for fetching the initial Drawable resource. | ||
| 766 | val res = context.resources | ||
| 767 | |||
| 768 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad. | ||
| 769 | val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 770 | |||
| 771 | // Decide scale based on button ID and user preference | ||
| 772 | var scale = 0.40f | ||
| 773 | scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() | ||
| 774 | scale /= 100f | ||
| 775 | |||
| 776 | // Initialize the InputOverlayDrawableDpad. | ||
| 777 | val defaultStateBitmap = | ||
| 778 | getBitmap(context, defaultResId, scale) | ||
| 779 | val pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId, scale) | ||
| 780 | val pressedTwoDirectionsStateBitmap = | ||
| 781 | getBitmap(context, pressedTwoDirectionsResId, scale) | ||
| 782 | |||
| 783 | val overlayDrawable = InputOverlayDrawableDpad( | ||
| 784 | res, | ||
| 785 | defaultStateBitmap, | ||
| 786 | pressedOneDirectionStateBitmap, | ||
| 787 | pressedTwoDirectionsStateBitmap, | ||
| 788 | ButtonType.DPAD_UP, | ||
| 789 | ButtonType.DPAD_DOWN, | ||
| 790 | ButtonType.DPAD_LEFT, | ||
| 791 | ButtonType.DPAD_RIGHT | ||
| 792 | ) | ||
| 793 | |||
| 794 | // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. | ||
| 795 | // These were set in the input overlay configuration menu. | ||
| 796 | val drawableX = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f).toInt() | ||
| 797 | val drawableY = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f).toInt() | ||
| 798 | val width = overlayDrawable.width | ||
| 799 | val height = overlayDrawable.height | ||
| 800 | |||
| 801 | // Now set the bounds for the InputOverlayDrawableDpad. | ||
| 802 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be. | ||
| 803 | overlayDrawable.setBounds( | ||
| 804 | drawableX - (width / 2), | ||
| 805 | drawableY - (height / 2), | ||
| 806 | drawableX + (width / 2), | ||
| 807 | drawableY + (height / 2) | ||
| 808 | ) | ||
| 809 | |||
| 810 | // Need to set the image's position | ||
| 811 | overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2)) | ||
| 812 | return overlayDrawable | ||
| 813 | } | ||
| 814 | |||
| 815 | /** | ||
| 816 | * Initializes an [InputOverlayDrawableJoystick] | ||
| 817 | * | ||
| 818 | * @param context The current [Context] | ||
| 819 | * @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds). | ||
| 820 | * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around). | ||
| 821 | * @param pressedResInner Resource ID for the pressed inner image of the joystick. | ||
| 822 | * @param joystick Identifier for which joystick this is. | ||
| 823 | * @param button Identifier for which joystick button this is. | ||
| 824 | * @return the initialized [InputOverlayDrawableJoystick]. | ||
| 825 | */ | ||
| 826 | private fun initializeOverlayJoystick( | ||
| 827 | context: Context, | ||
| 828 | resOuter: Int, | ||
| 829 | defaultResInner: Int, | ||
| 830 | pressedResInner: Int, | ||
| 831 | joystick: Int, | ||
| 832 | button: Int, | ||
| 833 | orientation: String | ||
| 834 | ): InputOverlayDrawableJoystick { | ||
| 835 | // Resources handle for fetching the initial Drawable resource. | ||
| 836 | val res = context.resources | ||
| 837 | |||
| 838 | // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick. | ||
| 839 | val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 840 | |||
| 841 | // Decide scale based on user preference | ||
| 842 | var scale = 0.40f | ||
| 843 | scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat() | ||
| 844 | scale /= 100f | ||
| 845 | |||
| 846 | // Initialize the InputOverlayDrawableJoystick. | ||
| 847 | val bitmapOuter = getBitmap(context, resOuter, scale) | ||
| 848 | val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f) | ||
| 849 | val bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f) | ||
| 850 | |||
| 851 | // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. | ||
| 852 | // These were set in the input overlay configuration menu. | ||
| 853 | val drawableX = sPrefs.getFloat("$button$orientation-X", 0f).toInt() | ||
| 854 | val drawableY = sPrefs.getFloat("$button$orientation-Y", 0f).toInt() | ||
| 855 | val outerScale = 1.66f | ||
| 856 | |||
| 857 | // Now set the bounds for the InputOverlayDrawableJoystick. | ||
| 858 | // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be. | ||
| 859 | val outerSize = bitmapOuter.width | ||
| 860 | val outerRect = Rect( | ||
| 861 | drawableX - (outerSize / 2), | ||
| 862 | drawableY - (outerSize / 2), | ||
| 863 | drawableX + (outerSize / 2), | ||
| 864 | drawableY + (outerSize / 2) | ||
| 865 | ) | ||
| 866 | val innerRect = | ||
| 867 | Rect(0, 0, (outerSize / outerScale).toInt(), (outerSize / outerScale).toInt()) | ||
| 868 | |||
| 869 | // Send the drawableId to the joystick so it can be referenced when saving control position. | ||
| 870 | val overlayDrawable = InputOverlayDrawableJoystick( | ||
| 871 | res, | ||
| 872 | bitmapOuter, | ||
| 873 | bitmapInnerDefault, | ||
| 874 | bitmapInnerPressed, | ||
| 875 | outerRect, | ||
| 876 | innerRect, | ||
| 877 | joystick, | ||
| 878 | button | ||
| 879 | ) | ||
| 880 | |||
| 881 | // Need to set the image's position | ||
| 882 | overlayDrawable.setPosition(drawableX, drawableY) | ||
| 883 | return overlayDrawable | ||
| 884 | } | ||
| 885 | } | ||
| 886 | } | ||