mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 11:21:44 +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>
|
||||||
|
|
||||||
|
|
||||||
|
<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" />
|
<service android:name="org.citra.citra_emu.utils.ForegroundService" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -7,31 +7,34 @@ package org.citra.citra_emu.applets;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
import java.util.Objects;
|
||||||
import org.citra.citra_emu.CitraApplication;
|
import org.citra.citra_emu.CitraApplication;
|
||||||
import org.citra.citra_emu.NativeLibrary;
|
import org.citra.citra_emu.NativeLibrary;
|
||||||
import org.citra.citra_emu.R;
|
import org.citra.citra_emu.R;
|
||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
import org.citra.citra_emu.activities.EmulationActivity;
|
||||||
import org.citra.citra_emu.utils.Log;
|
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 {
|
public final class SoftwareKeyboard {
|
||||||
/// Corresponds to Frontend::ButtonConfig
|
/// Corresponds to Frontend::ButtonConfig
|
||||||
private interface ButtonConfig {
|
public interface ButtonConfig {
|
||||||
int Single = 0; /// Ok button
|
int Single = 0; /// Ok button
|
||||||
int Dual = 1; /// Cancel | Ok buttons
|
int Dual = 1; /// Cancel | Ok buttons
|
||||||
int Triple = 2; /// Cancel | I Forgot | Ok buttons
|
int Triple = 2; /// Cancel | I Forgot | Ok buttons
|
||||||
|
@ -62,8 +65,7 @@ public final class SoftwareKeyboard {
|
||||||
public int max_text_length;
|
public int max_text_length;
|
||||||
public boolean multiline_mode; /// True if the keyboard accepts multiple lines of input
|
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
|
public String hint_text; /// Displayed in the field as a hint before
|
||||||
@Nullable
|
@Nullable public String[] button_text; /// Contains the button text that the caller provides
|
||||||
public String[] button_text; /// Contains the button text that the caller provides
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Corresponds to Frontend::KeyboardData
|
/// 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
|
@Override
|
||||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
|
||||||
int dstart, int dend) {
|
int dstart, int dend) {
|
||||||
String text = new StringBuilder(dest)
|
String text = new StringBuilder(dest)
|
||||||
.replace(dstart, dend, source.subSequence(start, end).toString())
|
.replace(dstart, dend, source.subSequence(start, end).toString())
|
||||||
.toString();
|
.toString();
|
||||||
if (ValidateFilters(text) == ValidationError.None) {
|
if (ValidateFilters(text) == ValidationError.None) {
|
||||||
return null; // Accept replacement
|
return null; // Accept replacement
|
||||||
}
|
}
|
||||||
|
@ -107,52 +109,52 @@ public final class SoftwareKeyboard {
|
||||||
assert emulationActivity != null;
|
assert emulationActivity != null;
|
||||||
|
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
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 =
|
params.leftMargin = params.rightMargin =
|
||||||
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
||||||
R.dimen.dialog_margin);
|
R.dimen.dialog_margin);
|
||||||
|
|
||||||
KeyboardConfig config = Objects.requireNonNull(
|
KeyboardConfig config = Objects.requireNonNull(
|
||||||
(KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config"));
|
(KeyboardConfig)Objects.requireNonNull(getArguments()).getSerializable("config"));
|
||||||
|
|
||||||
// Set up the input
|
// Set up the input
|
||||||
EditText editText = new EditText(CitraApplication.getAppContext());
|
EditText editText = new EditText(CitraApplication.getAppContext());
|
||||||
editText.setHint(config.hint_text);
|
editText.setHint(config.hint_text);
|
||||||
editText.setSingleLine(!config.multiline_mode);
|
editText.setSingleLine(!config.multiline_mode);
|
||||||
editText.setLayoutParams(params);
|
editText.setLayoutParams(params);
|
||||||
editText.setFilters(new InputFilter[]{
|
editText.setFilters(new InputFilter[] {
|
||||||
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
||||||
|
|
||||||
FrameLayout container = new FrameLayout(emulationActivity);
|
FrameLayout container = new FrameLayout(emulationActivity);
|
||||||
container.addView(editText);
|
container.addView(editText);
|
||||||
|
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
|
||||||
.setTitle(R.string.software_keyboard)
|
.setTitle(R.string.software_keyboard)
|
||||||
.setView(container);
|
.setView(container);
|
||||||
setCancelable(false);
|
setCancelable(false);
|
||||||
|
|
||||||
switch (config.button_config) {
|
switch (config.button_config) {
|
||||||
case ButtonConfig.Triple: {
|
case ButtonConfig.Triple: {
|
||||||
final String text = config.button_text[1].isEmpty()
|
final String text = config.button_text[1].isEmpty()
|
||||||
? emulationActivity.getString(R.string.i_forgot)
|
? emulationActivity.getString(R.string.i_forgot)
|
||||||
: config.button_text[1];
|
: config.button_text[1];
|
||||||
builder.setNeutralButton(text, null);
|
builder.setNeutralButton(text, null);
|
||||||
}
|
}
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case ButtonConfig.Dual: {
|
case ButtonConfig.Dual: {
|
||||||
final String text = config.button_text[0].isEmpty()
|
final String text = config.button_text[0].isEmpty()
|
||||||
? emulationActivity.getString(android.R.string.cancel)
|
? emulationActivity.getString(android.R.string.cancel)
|
||||||
: config.button_text[0];
|
: config.button_text[0];
|
||||||
builder.setNegativeButton(text, null);
|
builder.setNegativeButton(text, null);
|
||||||
}
|
}
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case ButtonConfig.Single: {
|
case ButtonConfig.Single: {
|
||||||
final String text = config.button_text[2].isEmpty()
|
final String text = config.button_text[2].isEmpty()
|
||||||
? emulationActivity.getString(android.R.string.ok)
|
? emulationActivity.getString(android.R.string.ok)
|
||||||
: config.button_text[2];
|
: config.button_text[2];
|
||||||
builder.setPositiveButton(text, null);
|
builder.setPositiveButton(text, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final AlertDialog dialog = builder.create();
|
final AlertDialog dialog = builder.create();
|
||||||
|
@ -209,31 +211,65 @@ public final class SoftwareKeyboard {
|
||||||
fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard");
|
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();
|
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||||
String message = "";
|
String message = "";
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case FixedLengthRequired:
|
case FixedLengthRequired:
|
||||||
message =
|
message =
|
||||||
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
|
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
|
||||||
break;
|
break;
|
||||||
case MaxLengthExceeded:
|
case MaxLengthExceeded:
|
||||||
message =
|
message =
|
||||||
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
|
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
|
||||||
break;
|
break;
|
||||||
case BlankInputNotAllowed:
|
case BlankInputNotAllowed:
|
||||||
message = emulationActivity.getString(R.string.blank_input_not_allowed);
|
message = emulationActivity.getString(R.string.blank_input_not_allowed);
|
||||||
break;
|
break;
|
||||||
case EmptyInputNotAllowed:
|
case EmptyInputNotAllowed:
|
||||||
message = emulationActivity.getString(R.string.empty_input_not_allowed);
|
message = emulationActivity.getString(R.string.empty_input_not_allowed);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
new MaterialAlertDialogBuilder(emulationActivity)
|
// TODO show error dialog
|
||||||
.setTitle(R.string.software_keyboard)
|
Log.warning("Keyboard error: " + message);
|
||||||
.setMessage(message)
|
/* new MaterialAlertDialogBuilder(emulationActivity)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setTitle(R.string.software_keyboard)
|
||||||
.show();
|
.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) {
|
public static KeyboardData Execute(KeyboardConfig config) {
|
||||||
|
@ -242,7 +278,12 @@ public final class SoftwareKeyboard {
|
||||||
return new KeyboardData(0, "");
|
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) {
|
synchronized (finishLock) {
|
||||||
try {
|
try {
|
||||||
|
@ -256,11 +297,11 @@ public final class SoftwareKeyboard {
|
||||||
|
|
||||||
public static void ShowError(String error) {
|
public static void ShowError(String error) {
|
||||||
NativeLibrary.displayAlertMsg(
|
NativeLibrary.displayAlertMsg(
|
||||||
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
||||||
error, false);
|
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;
|
package org.citra.citra_emu.vr;
|
||||||
|
|
||||||
public class ErrorMessageLayer
|
public class ErrorMessageLayer {
|
||||||
{
|
|
||||||
|
|
||||||
public static ErrorMessageLayer instance = null;
|
public static ErrorMessageLayer instance = null;
|
||||||
|
|
||||||
public static void showErrorWindow(final String titleStr,
|
public static void showErrorWindow(final String titleStr, final String mainMessageStr) {}
|
||||||
final String mainMessageStr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void _showErrorWindow(final String titleStr,
|
public void _showErrorWindow(final String titleStr, final String mainMessageStr) {}
|
||||||
final String mainMessageStr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideErrorWindow() {}
|
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
|
* Note: this is set up to require the min number of changes possible to
|
||||||
*existing Citra code, in case an upstream merge is desired.
|
*existing Citra code, in case an upstream merge is desired.
|
||||||
**/
|
**/
|
||||||
public class GameSurfaceLayer
|
public class GameSurfaceLayer {
|
||||||
{
|
public static void setSurface(VrActivity activity, Surface surface) {
|
||||||
public static void setSurface(VrActivity activity, Surface surface)
|
|
||||||
{
|
|
||||||
assert activity != null;
|
assert activity != null;
|
||||||
((EmulationFragment)activity.getSupportFragmentManager()
|
((EmulationFragment)activity.getSupportFragmentManager().findFragmentById(
|
||||||
.findFragmentById(R.id.frame_emulation_fragment))
|
R.id.frame_emulation_fragment))
|
||||||
.surfaceCreated(surface);
|
.surfaceCreated(surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,52 +10,53 @@ import android.os.Bundle;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import org.citra.citra_emu.NativeLibrary;
|
import org.citra.citra_emu.NativeLibrary;
|
||||||
import org.citra.citra_emu.activities.EmulationActivity;
|
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.ui.SettingsActivity;
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile;
|
import org.citra.citra_emu.features.settings.utils.SettingsFile;
|
||||||
import org.citra.citra_emu.utils.Log;
|
import org.citra.citra_emu.utils.Log;
|
||||||
|
public class VrActivity extends EmulationActivity {
|
||||||
public class VrActivity extends EmulationActivity
|
|
||||||
{
|
|
||||||
|
|
||||||
private long mHandle = 0;
|
private long mHandle = 0;
|
||||||
public static boolean hasRun = false;
|
public static boolean hasRun = false;
|
||||||
public static VrActivity currentActivity = null;
|
public static VrActivity currentActivity = null;
|
||||||
ClickRunnable clickRunnable = new ClickRunnable();
|
ClickRunnable clickRunnable = new ClickRunnable();
|
||||||
|
|
||||||
static { System.loadLibrary("openxr_forwardloader.oculus"); }
|
static {
|
||||||
public static void launch(Context context, final String gamePath,
|
System.loadLibrary("openxr_forwardloader.oculus");
|
||||||
final String gameTitle)
|
}
|
||||||
{
|
|
||||||
|
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);
|
Intent intent = new Intent(context, VrActivity.class);
|
||||||
final int mainDisplayId = getMainDisplay(context);
|
final int mainDisplayId = getMainDisplay(context);
|
||||||
if (mainDisplayId < 0)
|
if (mainDisplayId < 0) {
|
||||||
{
|
|
||||||
// TODO handle error
|
// TODO handle error
|
||||||
throw new RuntimeException("Could not find main display");
|
throw new RuntimeException("Could not find main display");
|
||||||
}
|
}
|
||||||
ActivityOptions options =
|
ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(mainDisplayId);
|
||||||
ActivityOptions.makeBasic().setLaunchDisplayId(mainDisplayId);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
|
||||||
intent.setFlags(
|
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
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_GAME, gamePath);
|
||||||
intent.putExtra(EmulationActivity.EXTRA_SELECTED_TITLE, gameTitle);
|
intent.putExtra(EmulationActivity.EXTRA_SELECTED_TITLE, gameTitle);
|
||||||
if (context instanceof ContextWrapper)
|
if (context instanceof ContextWrapper) {
|
||||||
{
|
|
||||||
ContextWrapper contextWrapper = (ContextWrapper)context;
|
ContextWrapper contextWrapper = (ContextWrapper)context;
|
||||||
Context baseContext = contextWrapper.getBaseContext();
|
Context baseContext = contextWrapper.getBaseContext();
|
||||||
baseContext.startActivity(intent, options.toBundle());
|
baseContext.startActivity(intent, options.toBundle());
|
||||||
|
} else {
|
||||||
|
context.startActivity(intent, options.toBundle());
|
||||||
}
|
}
|
||||||
else { context.startActivity(intent, options.toBundle()); }
|
|
||||||
((Activity)(context)).finish();
|
((Activity)(context)).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void onCreate(Bundle savedInstanceState)
|
@Override
|
||||||
{
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
if (hasRun)
|
if (hasRun) {
|
||||||
{
|
|
||||||
Log.info("VRActivity already existed");
|
Log.info("VRActivity already existed");
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -67,35 +68,37 @@ public class VrActivity extends EmulationActivity
|
||||||
// TODO assert mHandle != null
|
// TODO assert mHandle != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void onDestroy()
|
@Override
|
||||||
{
|
protected void onDestroy() {
|
||||||
Log.info("VR [Java] onDestroy");
|
Log.info("VR [Java] onDestroy");
|
||||||
currentActivity = null;
|
currentActivity = null;
|
||||||
if (mHandle != 0) { nativeOnDestroy(mHandle); }
|
if (mHandle != 0) {
|
||||||
|
nativeOnDestroy(mHandle);
|
||||||
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onStart()
|
@Override
|
||||||
{
|
public void onStart() {
|
||||||
Log.info("VR [Java] onStart");
|
Log.info("VR [Java] onStart");
|
||||||
System.gc();
|
System.gc();
|
||||||
super.onStart();
|
super.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onResume()
|
@Override
|
||||||
{
|
public void onResume() {
|
||||||
Log.info("VR [Java] onResume");
|
Log.info("VR [Java] onResume");
|
||||||
super.onResume();
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onPause()
|
@Override
|
||||||
{
|
public void onPause() {
|
||||||
Log.info("VR [Java] onPause");
|
Log.info("VR [Java] onPause");
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onStop()
|
@Override
|
||||||
{
|
public void onStop() {
|
||||||
Log.info("VR [Java] onStop");
|
Log.info("VR [Java] onStop");
|
||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
@ -103,97 +106,90 @@ public class VrActivity extends EmulationActivity
|
||||||
private native long nativeOnCreate();
|
private native long nativeOnCreate();
|
||||||
private native void nativeOnDestroy(final long handle);
|
private native void nativeOnDestroy(final long handle);
|
||||||
|
|
||||||
private static int getMainDisplay(Context context)
|
private static int getMainDisplay(Context context) {
|
||||||
{
|
|
||||||
final DisplayManager displayManager =
|
final DisplayManager displayManager =
|
||||||
(DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
|
(DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
|
||||||
Display[] displays = displayManager.getDisplays();
|
Display[] displays = displayManager.getDisplays();
|
||||||
for (int i = 0; i < displays.length; i++)
|
for (int i = 0; i < displays.length; i++) {
|
||||||
{
|
if (displays[i].getDisplayId() == Display.DEFAULT_DISPLAY) {
|
||||||
if (displays[i].getDisplayId() == Display.DEFAULT_DISPLAY)
|
|
||||||
{
|
|
||||||
return displays[i].getDisplayId();
|
return displays[i].getDisplayId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void finishActivity()
|
public void finishActivity() {
|
||||||
{
|
if (!isFinishing()) {
|
||||||
if (!isFinishing()) { finish(); }
|
finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void forwardVRInput(final int keycode, final boolean isPressed)
|
void forwardVRInput(final int keycode, final boolean isPressed) {
|
||||||
{
|
KeyEvent event =
|
||||||
KeyEvent event = new KeyEvent(
|
new KeyEvent(isPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode);
|
||||||
isPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode);
|
|
||||||
event.setSource(InputDevice.SOURCE_GAMEPAD);
|
event.setSource(InputDevice.SOURCE_GAMEPAD);
|
||||||
dispatchKeyEvent(event);
|
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
|
// dispatch joystick input as gamepad joystick input
|
||||||
NativeLibrary.onGamePadMoveEvent(
|
NativeLibrary.onGamePadMoveEvent("Quest controller",
|
||||||
"Quest controller",
|
joystickType == 0 ? NativeLibrary.ButtonType.STICK_C
|
||||||
joystickType == 0 ? NativeLibrary.ButtonType.STICK_C
|
: NativeLibrary.ButtonType.STICK_LEFT,
|
||||||
: NativeLibrary.ButtonType.STICK_LEFT,
|
x, -y);
|
||||||
x, -y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSettingsMenu()
|
void openSettingsMenu() {
|
||||||
{
|
|
||||||
SettingsActivity.launch(this, SettingsFile.FILE_NAME_CONFIG, "");
|
SettingsActivity.launch(this, SettingsFile.FILE_NAME_CONFIG, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendClickToWindow(final float x, final float y,
|
public void sendClickToWindow(final float x, final float y, final int motionType) {
|
||||||
final int motionType)
|
|
||||||
{
|
|
||||||
clickRunnable.updateState((int)x, (int)y, motionType);
|
clickRunnable.updateState((int)x, (int)y, motionType);
|
||||||
runOnUiThread(clickRunnable);
|
runOnUiThread(clickRunnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pauseGame()
|
public void pauseGame() {
|
||||||
{
|
|
||||||
Log.info("VR [Java] 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");
|
Log.info("VR [Java] resumeGame");
|
||||||
// this checks to make sure the emulation has started and pausing it is
|
// this checks to make sure the emulation has started and pausing it is
|
||||||
// safe -- not whether it's paused/resumed
|
// 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 xPosition;
|
||||||
private int yPosition;
|
private int yPosition;
|
||||||
private int motionType;
|
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.xPosition = x;
|
||||||
this.yPosition = y;
|
this.yPosition = y;
|
||||||
this.motionType = motionType;
|
this.motionType = motionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void run()
|
@Override
|
||||||
{
|
public void run() {
|
||||||
switch (motionType)
|
switch (motionType) {
|
||||||
{
|
case 0:
|
||||||
case 0: NativeLibrary.onTouchEvent(0, 0, false); break;
|
NativeLibrary.onTouchEvent(0, 0, false);
|
||||||
case 1:
|
break;
|
||||||
NativeLibrary.onTouchEvent(xPosition, yPosition, true);
|
case 1:
|
||||||
break;
|
NativeLibrary.onTouchEvent(xPosition, yPosition, true);
|
||||||
case 2: NativeLibrary.onTouchMoved(xPosition, yPosition); break;
|
break;
|
||||||
default:
|
case 2:
|
||||||
Log.error(
|
NativeLibrary.onTouchMoved(xPosition, yPosition);
|
||||||
"VR [Java] sendClickToWindow: unknown motionType: " +
|
break;
|
||||||
motionType);
|
default:
|
||||||
break;
|
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 -->
|
<!-- 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="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="preferences_vr">VR</string>
|
||||||
<string name="vr_background">VR Environment</string>
|
<string name="vr_background">VR Environment</string>
|
||||||
|
<string name="vr_keyboard_left"><</string>
|
||||||
|
<string name="vr_keyboard_right">></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -51,4 +51,26 @@
|
||||||
</style>
|
</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>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue