mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 03:11:40 +02:00
[VrKeyboardActivity] Add a hacky in-app keyboard that won't crash
This commit is contained in:
parent
5fec9c3579
commit
c90d0d27de
12 changed files with 865 additions and 162 deletions
|
@ -126,6 +126,22 @@
|
|||
</activity>
|
||||
|
||||
|
||||
<activity
|
||||
android:name="org.citra.citra_emu.vr.VrKeyboardActivity"
|
||||
android:exported="true"
|
||||
android:process=":vr_process"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:resizeableActivity="false"
|
||||
android:screenOrientation="landscape">
|
||||
|
||||
<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.oculus.intent.category.2D" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<service android:name="org.citra.citra_emu.utils.ForegroundService" />
|
||||
|
||||
<activity
|
||||
|
|
|
@ -7,31 +7,34 @@ package org.citra.citra_emu.applets;
|
|||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Spanned;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.citra.citra_emu.CitraApplication;
|
||||
import org.citra.citra_emu.NativeLibrary;
|
||||
import org.citra.citra_emu.R;
|
||||
import org.citra.citra_emu.activities.EmulationActivity;
|
||||
import org.citra.citra_emu.utils.Log;
|
||||
import org.citra.citra_emu.vr.VrActivity;
|
||||
import org.citra.citra_emu.vr.VrKeyboardActivity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
// Warning (amwatson): I had to tear through this pretty quickly because I didn't realize
|
||||
// there was a system keyboard. This is a pretty hack solution that will not
|
||||
// merge well.
|
||||
public final class SoftwareKeyboard {
|
||||
/// Corresponds to Frontend::ButtonConfig
|
||||
private interface ButtonConfig {
|
||||
public interface ButtonConfig {
|
||||
int Single = 0; /// Ok button
|
||||
int Dual = 1; /// Cancel | Ok buttons
|
||||
int Triple = 2; /// Cancel | I Forgot | Ok buttons
|
||||
|
@ -62,8 +65,7 @@ public final class SoftwareKeyboard {
|
|||
public int max_text_length;
|
||||
public boolean multiline_mode; /// True if the keyboard accepts multiple lines of input
|
||||
public String hint_text; /// Displayed in the field as a hint before
|
||||
@Nullable
|
||||
public String[] button_text; /// Contains the button text that the caller provides
|
||||
@Nullable public String[] button_text; /// Contains the button text that the caller provides
|
||||
}
|
||||
|
||||
/// Corresponds to Frontend::KeyboardData
|
||||
|
@ -77,13 +79,13 @@ public final class SoftwareKeyboard {
|
|||
}
|
||||
}
|
||||
|
||||
private static class Filter implements InputFilter {
|
||||
public static class Filter implements InputFilter {
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
|
||||
int dstart, int dend) {
|
||||
String text = new StringBuilder(dest)
|
||||
.replace(dstart, dend, source.subSequence(start, end).toString())
|
||||
.toString();
|
||||
.replace(dstart, dend, source.subSequence(start, end).toString())
|
||||
.toString();
|
||||
if (ValidateFilters(text) == ValidationError.None) {
|
||||
return null; // Accept replacement
|
||||
}
|
||||
|
@ -107,52 +109,52 @@ public final class SoftwareKeyboard {
|
|||
assert emulationActivity != null;
|
||||
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = params.rightMargin =
|
||||
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.dialog_margin);
|
||||
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.dialog_margin);
|
||||
|
||||
KeyboardConfig config = Objects.requireNonNull(
|
||||
(KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config"));
|
||||
(KeyboardConfig)Objects.requireNonNull(getArguments()).getSerializable("config"));
|
||||
|
||||
// Set up the input
|
||||
EditText editText = new EditText(CitraApplication.getAppContext());
|
||||
editText.setHint(config.hint_text);
|
||||
editText.setSingleLine(!config.multiline_mode);
|
||||
editText.setLayoutParams(params);
|
||||
editText.setFilters(new InputFilter[]{
|
||||
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
||||
editText.setFilters(new InputFilter[] {
|
||||
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
||||
|
||||
FrameLayout container = new FrameLayout(emulationActivity);
|
||||
container.addView(editText);
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
||||
.setTitle(R.string.software_keyboard)
|
||||
.setView(container);
|
||||
.setTitle(R.string.software_keyboard)
|
||||
.setView(container);
|
||||
setCancelable(false);
|
||||
|
||||
switch (config.button_config) {
|
||||
case ButtonConfig.Triple: {
|
||||
final String text = config.button_text[1].isEmpty()
|
||||
? emulationActivity.getString(R.string.i_forgot)
|
||||
: config.button_text[1];
|
||||
builder.setNeutralButton(text, null);
|
||||
}
|
||||
// fallthrough
|
||||
case ButtonConfig.Dual: {
|
||||
final String text = config.button_text[0].isEmpty()
|
||||
? emulationActivity.getString(android.R.string.cancel)
|
||||
: config.button_text[0];
|
||||
builder.setNegativeButton(text, null);
|
||||
}
|
||||
// fallthrough
|
||||
case ButtonConfig.Single: {
|
||||
final String text = config.button_text[2].isEmpty()
|
||||
? emulationActivity.getString(android.R.string.ok)
|
||||
: config.button_text[2];
|
||||
builder.setPositiveButton(text, null);
|
||||
break;
|
||||
}
|
||||
case ButtonConfig.Triple: {
|
||||
final String text = config.button_text[1].isEmpty()
|
||||
? emulationActivity.getString(R.string.i_forgot)
|
||||
: config.button_text[1];
|
||||
builder.setNeutralButton(text, null);
|
||||
}
|
||||
// fallthrough
|
||||
case ButtonConfig.Dual: {
|
||||
final String text = config.button_text[0].isEmpty()
|
||||
? emulationActivity.getString(android.R.string.cancel)
|
||||
: config.button_text[0];
|
||||
builder.setNegativeButton(text, null);
|
||||
}
|
||||
// fallthrough
|
||||
case ButtonConfig.Single: {
|
||||
final String text = config.button_text[2].isEmpty()
|
||||
? emulationActivity.getString(android.R.string.ok)
|
||||
: config.button_text[2];
|
||||
builder.setPositiveButton(text, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
|
@ -209,31 +211,65 @@ public final class SoftwareKeyboard {
|
|||
fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard");
|
||||
}
|
||||
|
||||
private static void HandleValidationError(KeyboardConfig config, ValidationError error) {
|
||||
public static void HandleValidationError(KeyboardConfig config, ValidationError error) {
|
||||
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||
String message = "";
|
||||
switch (error) {
|
||||
case FixedLengthRequired:
|
||||
message =
|
||||
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
|
||||
break;
|
||||
case MaxLengthExceeded:
|
||||
message =
|
||||
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
|
||||
break;
|
||||
case BlankInputNotAllowed:
|
||||
message = emulationActivity.getString(R.string.blank_input_not_allowed);
|
||||
break;
|
||||
case EmptyInputNotAllowed:
|
||||
message = emulationActivity.getString(R.string.empty_input_not_allowed);
|
||||
break;
|
||||
case FixedLengthRequired:
|
||||
message =
|
||||
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
|
||||
break;
|
||||
case MaxLengthExceeded:
|
||||
message =
|
||||
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
|
||||
break;
|
||||
case BlankInputNotAllowed:
|
||||
message = emulationActivity.getString(R.string.blank_input_not_allowed);
|
||||
break;
|
||||
case EmptyInputNotAllowed:
|
||||
message = emulationActivity.getString(R.string.empty_input_not_allowed);
|
||||
break;
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(emulationActivity)
|
||||
.setTitle(R.string.software_keyboard)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
// TODO show error dialog
|
||||
Log.warning("Keyboard error: " + message);
|
||||
/* new MaterialAlertDialogBuilder(emulationActivity)
|
||||
.setTitle(R.string.software_keyboard)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();*/
|
||||
}
|
||||
|
||||
public static void onFinishVrKeyboardPositive(final String text, final KeyboardConfig config) {
|
||||
data = new KeyboardData(0, "");
|
||||
data.button = config.button_config;
|
||||
data.text = text;
|
||||
final ValidationError error = ValidateInput(data.text);
|
||||
if (error != ValidationError.None) {
|
||||
HandleValidationError(config, error);
|
||||
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||
onFinishVrKeyboardNegative();
|
||||
return;
|
||||
}
|
||||
synchronized (finishLock) {
|
||||
finishLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onFinishVrKeyboardNeutral() {
|
||||
data = new KeyboardData(0, "");
|
||||
data.button = 1;
|
||||
synchronized (finishLock) {
|
||||
finishLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onFinishVrKeyboardNegative() {
|
||||
data = new KeyboardData(0, "");
|
||||
data.button = 0;
|
||||
synchronized (finishLock) {
|
||||
finishLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyboardData Execute(KeyboardConfig config) {
|
||||
|
@ -242,7 +278,12 @@ public final class SoftwareKeyboard {
|
|||
return new KeyboardData(0, "");
|
||||
}
|
||||
|
||||
NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config));
|
||||
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||
if (emulationActivity instanceof VrActivity) {
|
||||
((VrActivity)emulationActivity).mVrKeyboardLauncher.launch(config);
|
||||
} else {
|
||||
NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config));
|
||||
}
|
||||
|
||||
synchronized (finishLock) {
|
||||
try {
|
||||
|
@ -256,11 +297,11 @@ public final class SoftwareKeyboard {
|
|||
|
||||
public static void ShowError(String error) {
|
||||
NativeLibrary.displayAlertMsg(
|
||||
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
||||
error, false);
|
||||
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
||||
error, false);
|
||||
}
|
||||
|
||||
private static native ValidationError ValidateFilters(String text);
|
||||
public static native ValidationError ValidateFilters(String text);
|
||||
|
||||
private static native ValidationError ValidateInput(String text);
|
||||
public static native ValidationError ValidateInput(String text);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
package org.citra.citra_emu.vr;
|
||||
|
||||
public class ErrorMessageLayer
|
||||
{
|
||||
public class ErrorMessageLayer {
|
||||
|
||||
public static ErrorMessageLayer instance = null;
|
||||
|
||||
public static void showErrorWindow(final String titleStr,
|
||||
final String mainMessageStr)
|
||||
{
|
||||
}
|
||||
public static void showErrorWindow(final String titleStr, final String mainMessageStr) {}
|
||||
|
||||
public void _showErrorWindow(final String titleStr,
|
||||
final String mainMessageStr)
|
||||
{
|
||||
}
|
||||
public void _showErrorWindow(final String titleStr, final String mainMessageStr) {}
|
||||
|
||||
public void hideErrorWindow() {}
|
||||
|
||||
|
|
|
@ -14,13 +14,11 @@ import org.citra.citra_emu.fragments.EmulationFragment;
|
|||
* Note: this is set up to require the min number of changes possible to
|
||||
*existing Citra code, in case an upstream merge is desired.
|
||||
**/
|
||||
public class GameSurfaceLayer
|
||||
{
|
||||
public static void setSurface(VrActivity activity, Surface surface)
|
||||
{
|
||||
public class GameSurfaceLayer {
|
||||
public static void setSurface(VrActivity activity, Surface surface) {
|
||||
assert activity != null;
|
||||
((EmulationFragment)activity.getSupportFragmentManager()
|
||||
.findFragmentById(R.id.frame_emulation_fragment))
|
||||
((EmulationFragment)activity.getSupportFragmentManager().findFragmentById(
|
||||
R.id.frame_emulation_fragment))
|
||||
.surfaceCreated(surface);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,52 +10,53 @@ import android.os.Bundle;
|
|||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import org.citra.citra_emu.NativeLibrary;
|
||||
import org.citra.citra_emu.activities.EmulationActivity;
|
||||
import org.citra.citra_emu.applets.SoftwareKeyboard;
|
||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity;
|
||||
import org.citra.citra_emu.features.settings.utils.SettingsFile;
|
||||
import org.citra.citra_emu.utils.Log;
|
||||
|
||||
public class VrActivity extends EmulationActivity
|
||||
{
|
||||
public class VrActivity extends EmulationActivity {
|
||||
|
||||
private long mHandle = 0;
|
||||
public static boolean hasRun = false;
|
||||
public static VrActivity currentActivity = null;
|
||||
ClickRunnable clickRunnable = new ClickRunnable();
|
||||
|
||||
static { System.loadLibrary("openxr_forwardloader.oculus"); }
|
||||
public static void launch(Context context, final String gamePath,
|
||||
final String gameTitle)
|
||||
{
|
||||
static {
|
||||
System.loadLibrary("openxr_forwardloader.oculus");
|
||||
}
|
||||
|
||||
public final ActivityResultLauncher<SoftwareKeyboard.KeyboardConfig> mVrKeyboardLauncher =
|
||||
registerForActivityResult(new VrKeyboardActivity.Contract(),
|
||||
result -> VrKeyboardActivity.onFinishResult(result));
|
||||
|
||||
public static void launch(Context context, final String gamePath, final String gameTitle) {
|
||||
Intent intent = new Intent(context, VrActivity.class);
|
||||
final int mainDisplayId = getMainDisplay(context);
|
||||
if (mainDisplayId < 0)
|
||||
{
|
||||
if (mainDisplayId < 0) {
|
||||
// TODO handle error
|
||||
throw new RuntimeException("Could not find main display");
|
||||
}
|
||||
ActivityOptions options =
|
||||
ActivityOptions.makeBasic().setLaunchDisplayId(mainDisplayId);
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(mainDisplayId);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra(EmulationActivity.EXTRA_SELECTED_GAME, gamePath);
|
||||
intent.putExtra(EmulationActivity.EXTRA_SELECTED_TITLE, gameTitle);
|
||||
if (context instanceof ContextWrapper)
|
||||
{
|
||||
if (context instanceof ContextWrapper) {
|
||||
ContextWrapper contextWrapper = (ContextWrapper)context;
|
||||
Context baseContext = contextWrapper.getBaseContext();
|
||||
baseContext.startActivity(intent, options.toBundle());
|
||||
} else {
|
||||
context.startActivity(intent, options.toBundle());
|
||||
}
|
||||
else { context.startActivity(intent, options.toBundle()); }
|
||||
((Activity)(context)).finish();
|
||||
}
|
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
if (hasRun)
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (hasRun) {
|
||||
Log.info("VRActivity already existed");
|
||||
finish();
|
||||
}
|
||||
|
@ -67,35 +68,37 @@ public class VrActivity extends EmulationActivity
|
|||
// TODO assert mHandle != null
|
||||
}
|
||||
|
||||
@Override protected void onDestroy()
|
||||
{
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.info("VR [Java] onDestroy");
|
||||
currentActivity = null;
|
||||
if (mHandle != 0) { nativeOnDestroy(mHandle); }
|
||||
if (mHandle != 0) {
|
||||
nativeOnDestroy(mHandle);
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override public void onStart()
|
||||
{
|
||||
@Override
|
||||
public void onStart() {
|
||||
Log.info("VR [Java] onStart");
|
||||
System.gc();
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override public void onResume()
|
||||
{
|
||||
@Override
|
||||
public void onResume() {
|
||||
Log.info("VR [Java] onResume");
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override public void onPause()
|
||||
{
|
||||
@Override
|
||||
public void onPause() {
|
||||
Log.info("VR [Java] onPause");
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override public void onStop()
|
||||
{
|
||||
@Override
|
||||
public void onStop() {
|
||||
Log.info("VR [Java] onStop");
|
||||
super.onStop();
|
||||
}
|
||||
|
@ -103,97 +106,90 @@ public class VrActivity extends EmulationActivity
|
|||
private native long nativeOnCreate();
|
||||
private native void nativeOnDestroy(final long handle);
|
||||
|
||||
private static int getMainDisplay(Context context)
|
||||
{
|
||||
private static int getMainDisplay(Context context) {
|
||||
final DisplayManager displayManager =
|
||||
(DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
|
||||
Display[] displays = displayManager.getDisplays();
|
||||
for (int i = 0; i < displays.length; i++)
|
||||
{
|
||||
if (displays[i].getDisplayId() == Display.DEFAULT_DISPLAY)
|
||||
{
|
||||
for (int i = 0; i < displays.length; i++) {
|
||||
if (displays[i].getDisplayId() == Display.DEFAULT_DISPLAY) {
|
||||
return displays[i].getDisplayId();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void finishActivity()
|
||||
{
|
||||
if (!isFinishing()) { finish(); }
|
||||
public void finishActivity() {
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void forwardVRInput(final int keycode, final boolean isPressed)
|
||||
{
|
||||
KeyEvent event = new KeyEvent(
|
||||
isPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode);
|
||||
void forwardVRInput(final int keycode, final boolean isPressed) {
|
||||
KeyEvent event =
|
||||
new KeyEvent(isPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode);
|
||||
event.setSource(InputDevice.SOURCE_GAMEPAD);
|
||||
dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
void forwardVRJoystick(final float x, final float y, final int joystickType)
|
||||
{
|
||||
void forwardVRJoystick(final float x, final float y, final int joystickType) {
|
||||
// dispatch joystick input as gamepad joystick input
|
||||
NativeLibrary.onGamePadMoveEvent(
|
||||
"Quest controller",
|
||||
joystickType == 0 ? NativeLibrary.ButtonType.STICK_C
|
||||
: NativeLibrary.ButtonType.STICK_LEFT,
|
||||
x, -y);
|
||||
NativeLibrary.onGamePadMoveEvent("Quest controller",
|
||||
joystickType == 0 ? NativeLibrary.ButtonType.STICK_C
|
||||
: NativeLibrary.ButtonType.STICK_LEFT,
|
||||
x, -y);
|
||||
}
|
||||
|
||||
void openSettingsMenu()
|
||||
{
|
||||
void openSettingsMenu() {
|
||||
SettingsActivity.launch(this, SettingsFile.FILE_NAME_CONFIG, "");
|
||||
}
|
||||
|
||||
public void sendClickToWindow(final float x, final float y,
|
||||
final int motionType)
|
||||
{
|
||||
public void sendClickToWindow(final float x, final float y, final int motionType) {
|
||||
clickRunnable.updateState((int)x, (int)y, motionType);
|
||||
runOnUiThread(clickRunnable);
|
||||
}
|
||||
|
||||
public void pauseGame()
|
||||
{
|
||||
public void pauseGame() {
|
||||
Log.info("VR [Java] pauseGame");
|
||||
if (NativeLibrary.IsRunning()) { NativeLibrary.PauseEmulation(); }
|
||||
if (NativeLibrary.IsRunning()) {
|
||||
NativeLibrary.PauseEmulation();
|
||||
}
|
||||
}
|
||||
|
||||
public void resumeGame()
|
||||
{
|
||||
public void resumeGame() {
|
||||
Log.info("VR [Java] resumeGame");
|
||||
// this checks to make sure the emulation has started and pausing it is
|
||||
// safe -- not whether it's paused/resumed
|
||||
if (NativeLibrary.IsRunning()) { NativeLibrary.UnPauseEmulation(); }
|
||||
if (NativeLibrary.IsRunning()) {
|
||||
NativeLibrary.UnPauseEmulation();
|
||||
}
|
||||
}
|
||||
|
||||
class ClickRunnable implements Runnable
|
||||
{
|
||||
class ClickRunnable implements Runnable {
|
||||
private int xPosition;
|
||||
private int yPosition;
|
||||
private int motionType;
|
||||
|
||||
public void updateState(int x, int y, int motionType)
|
||||
{
|
||||
public void updateState(int x, int y, int motionType) {
|
||||
this.xPosition = x;
|
||||
this.yPosition = y;
|
||||
this.motionType = motionType;
|
||||
}
|
||||
|
||||
@Override public void run()
|
||||
{
|
||||
switch (motionType)
|
||||
{
|
||||
case 0: NativeLibrary.onTouchEvent(0, 0, false); break;
|
||||
case 1:
|
||||
NativeLibrary.onTouchEvent(xPosition, yPosition, true);
|
||||
break;
|
||||
case 2: NativeLibrary.onTouchMoved(xPosition, yPosition); break;
|
||||
default:
|
||||
Log.error(
|
||||
"VR [Java] sendClickToWindow: unknown motionType: " +
|
||||
motionType);
|
||||
break;
|
||||
@Override
|
||||
public void run() {
|
||||
switch (motionType) {
|
||||
case 0:
|
||||
NativeLibrary.onTouchEvent(0, 0, false);
|
||||
break;
|
||||
case 1:
|
||||
NativeLibrary.onTouchEvent(xPosition, yPosition, true);
|
||||
break;
|
||||
case 2:
|
||||
NativeLibrary.onTouchMoved(xPosition, yPosition);
|
||||
break;
|
||||
default:
|
||||
Log.error("VR [Java] sendClickToWindow: unknown motionType: " + motionType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
package org.citra.citra_emu.vr;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import org.citra.citra_emu.CitraApplication;
|
||||
import org.citra.citra_emu.R;
|
||||
import org.citra.citra_emu.applets.SoftwareKeyboard;
|
||||
import org.citra.citra_emu.utils.Log;
|
||||
|
||||
public class VrKeyboardActivity extends android.app.Activity {
|
||||
|
||||
private static final String EXTRA_KEYBOARD_INPUT_CONFIG =
|
||||
"org.citra.citra_emu.vr.KEYBOARD_INPUT_CONFIG";
|
||||
private static final String EXTRA_KEYBOARD_RESULT = "org.citra.citra_emu.vr.KEYBOARD_RESULT";
|
||||
|
||||
public static class Result implements Serializable {
|
||||
public static enum Type { None, Positive, Neutral, Negative }
|
||||
;
|
||||
public Result() {
|
||||
text = "";
|
||||
type = Type.None;
|
||||
config = null;
|
||||
}
|
||||
public Result(final String text, final Type type,
|
||||
final SoftwareKeyboard.KeyboardConfig config) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Result(final Type type) {
|
||||
this.text = "";
|
||||
this.type = type;
|
||||
this.config = null;
|
||||
}
|
||||
|
||||
public String text;
|
||||
public Type type;
|
||||
public SoftwareKeyboard.KeyboardConfig config;
|
||||
}
|
||||
|
||||
public static class Contract
|
||||
extends ActivityResultContract<SoftwareKeyboard.KeyboardConfig, Result> {
|
||||
@Override
|
||||
public Intent createIntent(Context context, final SoftwareKeyboard.KeyboardConfig config) {
|
||||
Intent intent = new Intent(context, VrKeyboardActivity.class);
|
||||
intent.putExtra(EXTRA_KEYBOARD_INPUT_CONFIG, config);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result parseResult(int resultCode, Intent intent) {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
Log.warning("parseResult(): Unexpected result code: " + resultCode);
|
||||
return new Result();
|
||||
}
|
||||
if (intent != null) {
|
||||
final Result result = (Result)intent.getSerializableExtra(EXTRA_KEYBOARD_RESULT);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Log.warning("parseResult(): finished with OK, but no result. Intent: " + intent);
|
||||
return new Result();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onFinishResult(final Result result) {
|
||||
switch (result.type) {
|
||||
case Positive:
|
||||
SoftwareKeyboard.onFinishVrKeyboardPositive(result.text, result.config);
|
||||
break;
|
||||
case Neutral:
|
||||
SoftwareKeyboard.onFinishVrKeyboardNeutral();
|
||||
break;
|
||||
case Negative:
|
||||
case None:
|
||||
SoftwareKeyboard.onFinishVrKeyboardNegative();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static enum KeyboardType { None, Abc, Num }
|
||||
|
||||
private EditText mEditText = null;
|
||||
private boolean mIsShifted = false;
|
||||
private KeyboardType mKeyboardTypeCur = KeyboardType.None;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
SoftwareKeyboard.KeyboardConfig config = new SoftwareKeyboard.KeyboardConfig();
|
||||
if (extras != null) {
|
||||
config = (SoftwareKeyboard.KeyboardConfig)extras.getSerializable(
|
||||
EXTRA_KEYBOARD_INPUT_CONFIG);
|
||||
}
|
||||
|
||||
setContentView(R.layout.vr_keyboard);
|
||||
mEditText = findViewById(R.id.vrKeyboardText);
|
||||
|
||||
{
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = params.rightMargin =
|
||||
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.dialog_margin);
|
||||
mEditText.setHint(config.hint_text);
|
||||
mEditText.setSingleLine(!config.multiline_mode);
|
||||
mEditText.setLayoutParams(params);
|
||||
mEditText.setFilters(
|
||||
new InputFilter[] {new SoftwareKeyboard.Filter(),
|
||||
new InputFilter.LengthFilter(config.max_text_length)});
|
||||
}
|
||||
|
||||
// Needed to show cursor onscreen.
|
||||
mEditText.requestFocus();
|
||||
WindowCompat.getInsetsController(getWindow(), mEditText)
|
||||
.show(WindowInsetsCompat.Type.ime());
|
||||
|
||||
setupResultButtons(config);
|
||||
showKeyboardType(KeyboardType.Abc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (!hasFocus) {
|
||||
finish(); // Finish the activity when it loses focus, like an AlertDialog.
|
||||
}
|
||||
}
|
||||
|
||||
private void setupResultButtons(final SoftwareKeyboard.KeyboardConfig config) {
|
||||
// Configure the result buttons
|
||||
findViewById(R.id.keyPositive).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra(
|
||||
EXTRA_KEYBOARD_RESULT,
|
||||
new Result(mEditText.getText().toString(), Result.Type.Positive, config));
|
||||
setResult(Activity.RESULT_OK, resultIntent);
|
||||
finish();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.keyNeutral).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra(EXTRA_KEYBOARD_RESULT, new Result(Result.Type.Neutral));
|
||||
setResult(Activity.RESULT_OK, resultIntent);
|
||||
finish();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.keyNegative).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra(EXTRA_KEYBOARD_RESULT, new Result(Result.Type.Negative));
|
||||
setResult(Activity.RESULT_OK, resultIntent);
|
||||
finish();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
switch (config.button_config) {
|
||||
case SoftwareKeyboard.ButtonConfig.Triple:
|
||||
findViewById(R.id.keyNeutral).setVisibility(View.VISIBLE);
|
||||
// fallthrough
|
||||
case SoftwareKeyboard.ButtonConfig.Dual:
|
||||
findViewById(R.id.keyNegative).setVisibility(View.VISIBLE);
|
||||
// fallthrough
|
||||
case SoftwareKeyboard.ButtonConfig.Single:
|
||||
findViewById(R.id.keyPositive).setVisibility(View.VISIBLE);
|
||||
// fallthrough
|
||||
case SoftwareKeyboard.ButtonConfig.None:
|
||||
break;
|
||||
default:
|
||||
Log.error("Unknown button config: " + config.button_config);
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
private void showKeyboardType(final KeyboardType keyboardType) {
|
||||
if (mKeyboardTypeCur == keyboardType) {
|
||||
return;
|
||||
}
|
||||
mKeyboardTypeCur = keyboardType;
|
||||
final ViewGroup keyboard = findViewById(R.id.vr_keyboard_keyboard);
|
||||
keyboard.removeAllViews();
|
||||
switch (keyboardType) {
|
||||
case Abc:
|
||||
getLayoutInflater().inflate(R.layout.vr_keyboard_abc, keyboard);
|
||||
addLetterKeyHandlersForViewGroup(keyboard, mIsShifted);
|
||||
break;
|
||||
case Num:
|
||||
getLayoutInflater().inflate(R.layout.vr_keyboard_123, keyboard);
|
||||
addLetterKeyHandlersForViewGroup(keyboard, false);
|
||||
break;
|
||||
default:
|
||||
assert false;
|
||||
}
|
||||
addModifierKeyHandlers();
|
||||
}
|
||||
|
||||
private void addModifierKeyHandlers() {
|
||||
findViewById(R.id.keyShift).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
setKeyCase(!mIsShifted);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// Note: I prefer touch listeners over click listeners because they activate
|
||||
// on the press instead of the release and therefore feel more responsive.
|
||||
findViewById(R.id.keyBackspace).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final String text = mEditText.getText().toString();
|
||||
if (text.length() > 0) {
|
||||
// Delete character before cursor
|
||||
final int position = mEditText.getSelectionStart();
|
||||
if (position > 0) {
|
||||
final String newText =
|
||||
text.substring(0, position - 1) + text.substring(position);
|
||||
mEditText.setText(newText);
|
||||
mEditText.setSelection(position - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.keySpace).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final int position = mEditText.getSelectionStart();
|
||||
if (position < mEditText.getText().length()) {
|
||||
final String newText =
|
||||
mEditText.getText().toString().substring(0, position) + " " +
|
||||
mEditText.getText().toString().substring(position);
|
||||
mEditText.setText(newText);
|
||||
mEditText.setSelection(position + 1);
|
||||
} else {
|
||||
mEditText.append(" ");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.keyLeft).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final int position = mEditText.getSelectionStart();
|
||||
if (position > 0) {
|
||||
mEditText.setSelection(position - 1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.keyRight).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final int position = mEditText.getSelectionStart();
|
||||
if (position < mEditText.getText().length()) {
|
||||
mEditText.setSelection(position + 1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.keyNumbers) != null) {
|
||||
findViewById(R.id.keyNumbers).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
showKeyboardType(KeyboardType.Num);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (findViewById(R.id.keyAbc) != null) {
|
||||
findViewById(R.id.keyAbc).setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
showKeyboardType(KeyboardType.Abc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addLetterKeyHandlersForViewGroup(final ViewGroup viewGroup,
|
||||
final boolean isShifted) {
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
final View child = viewGroup.getChildAt(i);
|
||||
if (child instanceof ViewGroup) {
|
||||
addLetterKeyHandlersForViewGroup((ViewGroup)child, isShifted);
|
||||
} else if (child instanceof Button) {
|
||||
if ("key_letter".equals(child.getTag())) {
|
||||
final Button key = (Button)child;
|
||||
key.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final int position = mEditText.getSelectionStart();
|
||||
if (position < mEditText.getText().length()) {
|
||||
final String newText =
|
||||
mEditText.getText().toString().substring(0, position) +
|
||||
key.getText().toString() +
|
||||
mEditText.getText().toString().substring(position);
|
||||
mEditText.setText(newText);
|
||||
mEditText.setSelection(position + 1);
|
||||
} else {
|
||||
mEditText.append(key.getText().toString());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setKeyCaseForButton(key, isShifted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeyCase(final boolean isShifted) {
|
||||
mIsShifted = isShifted;
|
||||
final ViewGroup layout = findViewById(R.id.vr_keyboard);
|
||||
setKeyCaseForViewGroup(layout, isShifted);
|
||||
}
|
||||
|
||||
private static void setKeyCaseForViewGroup(ViewGroup viewGroup, final boolean isShifted) {
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
final View child = viewGroup.getChildAt(i);
|
||||
if (child instanceof ViewGroup) {
|
||||
setKeyCaseForViewGroup((ViewGroup)child, isShifted);
|
||||
} else if (child instanceof Button && "key_letter".equals(child.getTag())) {
|
||||
setKeyCaseForButton((Button)child, isShifted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setKeyCaseForButton(Button button, final boolean isShifted) {
|
||||
final String text = button.getText().toString();
|
||||
if (isShifted) {
|
||||
button.setText(text.toUpperCase());
|
||||
} else {
|
||||
button.setText(text.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Pressed state -->
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?attr/colorSecondary"/> <!-- Pressed color -->
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<!-- Default state -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?attr/colorPrimary"/> <!-- Default color -->
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
74
src/android/app/src/main/res/layout/vr_keyboard.xml
Normal file
74
src/android/app/src/main/res/layout/vr_keyboard.xml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!--FML for having to write this f$%king keyboard.
|
||||
|
||||
I would not have had to do this were it not for a bug/idiosyncrasy
|
||||
in Quest's window manager that returns the wrong value for "is this window in
|
||||
focus?" when the packagename of the foregrounded window content is the same as
|
||||
the backgrounded immersive window (even when the two activities/windows are in
|
||||
different processes).
|
||||
|
||||
This bug prevents me from pulling up the Quest system keyboard.
|
||||
|
||||
Why it happens: Quest's window manager is performing a single check based on the
|
||||
app's packagename and returns information for the wrong window.-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:theme="@style/Theme.Citra.Main"
|
||||
android:paddingTop="40dp"
|
||||
android:id="@+id/vr_keyboard"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/vrKeyboardText"
|
||||
style="@style/VrKeyboardEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="text"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/vr_keyboard_keyboard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/keyPositive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:text="@android:string/ok" />
|
||||
<Button
|
||||
android:id="@+id/keyNeutral"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:text="@string/i_forgot" />
|
||||
<Button
|
||||
android:id="@+id/keyNegative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:text="@android:string/cancel" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
74
src/android/app/src/main/res/layout/vr_keyboard_123.xml
Normal file
74
src/android/app/src/main/res/layout/vr_keyboard_123.xml
Normal file
|
@ -0,0 +1,74 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Theme.Citra.Main"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
|
||||
<!-- Row for numbers -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/key1" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="1"/>
|
||||
<Button android:id="@+id/key2" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="2"/>
|
||||
<Button android:id="@+id/key3" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="3"/>
|
||||
<Button android:id="@+id/key4" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="4"/>
|
||||
<Button android:id="@+id/key5" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="5"/>
|
||||
<Button android:id="@+id/key6" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="6"/>
|
||||
<Button android:id="@+id/key7" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="7"/>
|
||||
<Button android:id="@+id/key8" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="8"/>
|
||||
<Button android:id="@+id/key9" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="9"/>
|
||||
<Button android:id="@+id/key0" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Row for symbols -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyAt" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="@"/>
|
||||
<Button android:id="@+id/keyHash" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="#"/>
|
||||
<Button android:id="@+id/keyDollar" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="$"/>
|
||||
<Button android:id="@+id/keyPercent" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="%"/>
|
||||
<Button android:id="@+id/keyAmpersand" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="&"/>
|
||||
<Button android:id="@+id/keyStar" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="*"/>
|
||||
<Button android:id="@+id/keyMinus" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="-"/>
|
||||
<Button android:id="@+id/keyPlus" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="+"/>
|
||||
<Button android:id="@+id/keyLParen" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="("/>
|
||||
<Button android:id="@+id/keyRParen" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text=")"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Row for more symbols and actions like delete or enter -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyShift" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.5" android:text="Shift"/>
|
||||
|
||||
<Button android:id="@+id/keyExclamation" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="!"/>
|
||||
<Button android:id="@+id/keyQuote" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="""/>
|
||||
<Button android:id="@+id/keyApostrophe" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="'"/>
|
||||
<Button android:id="@+id/keyColon" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text=":"/>
|
||||
<Button android:id="@+id/keySemicolon" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text=";"/>
|
||||
<Button android:id="@+id/keySlash" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="/"/>
|
||||
<Button android:id="@+id/keyQuestionMark" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="?"/>
|
||||
<Button android:id="@+id/keyBackspace" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.5" android:text="Del"/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyAbc" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="abc"/>
|
||||
<Button android:id="@+id/keySpace" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="4" android:text="Space"/>
|
||||
<Button android:id="@+id/keyLeft" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="<"/>
|
||||
<Button android:id="@+id/keyRight" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text=">"/>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
68
src/android/app/src/main/res/layout/vr_keyboard_abc.xml
Normal file
68
src/android/app/src/main/res/layout/vr_keyboard_abc.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:theme="@style/Theme.Citra.Main"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Row 1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyQ" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="q"/>
|
||||
<Button android:id="@+id/keyW" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="w"/>
|
||||
<Button android:id="@+id/keyE" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="e"/>
|
||||
<Button android:id="@+id/keyR" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="r"/>
|
||||
<Button android:id="@+id/keyT" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="t"/>
|
||||
<Button android:id="@+id/keyY" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="y"/>
|
||||
<Button android:id="@+id/keyU" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="u"/>
|
||||
<Button android:id="@+id/keyI" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="i"/>
|
||||
<Button android:id="@+id/keyO" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="o"/>
|
||||
<Button android:id="@+id/keyP" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="p"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyA" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="a"/>
|
||||
<Button android:id="@+id/keyS" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="s"/>
|
||||
<Button android:id="@+id/keyD" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="d"/>
|
||||
<Button android:id="@+id/keyF" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="f"/>
|
||||
<Button android:id="@+id/keyG" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="g"/>
|
||||
<Button android:id="@+id/keyH" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="h"/>
|
||||
<Button android:id="@+id/keyJ" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="J"/>
|
||||
<Button android:id="@+id/keyK" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="k"/>
|
||||
<Button android:id="@+id/keyL" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="l"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyShift" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.5" android:text="Shift"/>
|
||||
<Button android:id="@+id/keyZ" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="z"/>
|
||||
<Button android:id="@+id/keyX" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="x"/>
|
||||
<Button android:id="@+id/keyC" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="c"/>
|
||||
<Button android:id="@+id/keyV" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="v"/>
|
||||
<Button android:id="@+id/keyB" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="b"/>
|
||||
<Button android:id="@+id/keyN" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="n"/>
|
||||
<Button android:id="@+id/keyM" android:tag="key_letter" style="@style/VrKeyboardButtonStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="m"/>
|
||||
<Button android:id="@+id/keyBackspace" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.5" android:text="Del"/>
|
||||
</LinearLayout>
|
||||
<!-- Row 4 (Space, Enter, etc.) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button android:id="@+id/keyNumbers" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="123"/>
|
||||
<Button android:id="@+id/keySpace" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="4" android:text="Space"/>
|
||||
<Button android:id="@+id/keyLeft" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="<"/>
|
||||
<Button android:id="@+id/keyRight" android:tag="key_modifier" style="@style/VrKeyboardButtonStyleModifier" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text=">"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -290,7 +290,8 @@
|
|||
|
||||
<!-- VR-specific -->
|
||||
<string name="vr_gamepad_warning">Having trouble? Press @string/button_start + @string/button_select on the gamepad to toggle input modes</string>
|
||||
|
||||
<string name="preferences_vr">VR</string>
|
||||
<string name="vr_background">VR Environment</string>
|
||||
<string name="vr_keyboard_left"><</string>
|
||||
<string name="vr_keyboard_right">></string>
|
||||
</resources>
|
||||
|
|
|
@ -51,4 +51,26 @@
|
|||
</style>
|
||||
|
||||
|
||||
<!-- Style for keyboard buttons -->
|
||||
<style name="VrKeyboardButtonStyle" parent="Widget.Material3.Button">
|
||||
<item name="android:background">@drawable/vr_keyboard_key_background</item>
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:padding">10dp</item>
|
||||
<item name="android:layout_margin">2dp</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="colorPrimary">@color/citra_primary</item>
|
||||
</style>
|
||||
|
||||
<style name="VrKeyboardButtonStyleModifier" parent="@style/VrKeyboardButtonStyle">
|
||||
<item name="colorPrimary">@color/citra_secondary</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="VrKeyboardEditText" parent="Widget.AppCompat.EditText">
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:padding">12dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue