android: frontend: Implement basic software keyboard applet.
This commit is contained in:
parent
3ebfc5e97c
commit
2e8892eab4
@ -10,6 +10,9 @@ import android.app.AlertDialog;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.Surface;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.citra.citra_android.activities.EmulationActivity;
|
||||
import org.citra.citra_android.utils.Log;
|
||||
@ -26,7 +29,15 @@ public final class NativeLibrary {
|
||||
*/
|
||||
public static final String TouchScreenDevice = "Touchscreen";
|
||||
public static WeakReference<EmulationActivity> sEmulationActivity = new WeakReference<>(null);
|
||||
|
||||
private static boolean alertResult = false;
|
||||
private static String alertPromptResult = "";
|
||||
private static int alertPromptButton = 0;
|
||||
private static final Object alertPromptLock = new Object();
|
||||
private static boolean alertPromptInProgress = false;
|
||||
private static String alertPromptCaption = "";
|
||||
private static int alertPromptButtonConfig = 0;
|
||||
private static EditText alertPromptEditText = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
@ -353,6 +364,86 @@ public final class NativeLibrary {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void retryDisplayAlertPrompt() {
|
||||
if (!alertPromptInProgress) {
|
||||
return;
|
||||
}
|
||||
displayAlertPromptImpl(alertPromptCaption, alertPromptEditText.getText().toString(), alertPromptButtonConfig).show();
|
||||
}
|
||||
|
||||
public static String displayAlertPrompt(String caption, String text, int buttonConfig) {
|
||||
alertPromptCaption = caption;
|
||||
alertPromptButtonConfig = buttonConfig;
|
||||
alertPromptInProgress = true;
|
||||
|
||||
// Show the AlertDialog on the main thread
|
||||
sEmulationActivity.get().runOnUiThread(() -> displayAlertPromptImpl(alertPromptCaption, text, alertPromptButtonConfig).show());
|
||||
|
||||
// Wait for the lock to notify that it is complete
|
||||
synchronized (alertPromptLock) {
|
||||
try {
|
||||
alertPromptLock.wait();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
alertPromptInProgress = false;
|
||||
|
||||
return alertPromptResult;
|
||||
}
|
||||
|
||||
public static AlertDialog.Builder displayAlertPromptImpl(String caption, String text, int buttonConfig) {
|
||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
||||
alertPromptResult = "";
|
||||
alertPromptButton = 0;
|
||||
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.leftMargin = params.rightMargin = CitraApplication.getAppContext().getResources().getDimensionPixelSize(R.dimen.dialog_margin);
|
||||
|
||||
// Set up the input
|
||||
alertPromptEditText = new EditText(DolphinApplication.getAppContext());
|
||||
alertPromptEditText.setText(text);
|
||||
alertPromptEditText.setSingleLine();
|
||||
alertPromptEditText.setLayoutParams(params);
|
||||
|
||||
FrameLayout container = new FrameLayout(emulationActivity);
|
||||
container.addView(alertPromptEditText);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
|
||||
.setTitle(caption)
|
||||
.setView(container)
|
||||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) ->
|
||||
{
|
||||
alertPromptButton = buttonConfig;
|
||||
alertPromptResult = alertPromptEditText.getText().toString();
|
||||
synchronized (alertPromptLock) {
|
||||
alertPromptLock.notifyAll();
|
||||
}
|
||||
})
|
||||
.setOnDismissListener(dialogInterface ->
|
||||
{
|
||||
alertPromptResult = "";
|
||||
synchronized (alertPromptLock) {
|
||||
alertPromptLock.notifyAll();
|
||||
}
|
||||
});
|
||||
|
||||
if (buttonConfig > 0) {
|
||||
builder.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
|
||||
{
|
||||
alertPromptResult = "";
|
||||
synchronized (alertPromptLock) {
|
||||
alertPromptLock.notifyAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static int alertPromptButton() {
|
||||
return alertPromptButton;
|
||||
}
|
||||
|
||||
public static void setEmulationActivity(EmulationActivity emulationActivity) {
|
||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.");
|
||||
sEmulationActivity = new WeakReference<>(emulationActivity);
|
||||
|
@ -237,6 +237,9 @@ public final class EmulationActivity extends AppCompatActivity {
|
||||
mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE);
|
||||
mScreenPath = savedInstanceState.getString(EXTRA_SCREEN_PATH);
|
||||
mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION);
|
||||
|
||||
// If an alert prompt was in progress when state was restored, retry displaying it
|
||||
NativeLibrary.retryDisplayAlertPrompt();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,6 +17,8 @@ static JavaVM* s_java_vm;
|
||||
|
||||
static jclass s_native_library_class;
|
||||
static jmethodID s_display_alert_msg;
|
||||
static jmethodID s_display_alert_prompt;
|
||||
static jmethodID s_alert_prompt_button;
|
||||
static jmethodID s_is_portrait_mode;
|
||||
static jmethodID s_landscape_screen_layout;
|
||||
|
||||
@ -49,6 +51,14 @@ jmethodID GetDisplayAlertMsg() {
|
||||
return s_display_alert_msg;
|
||||
}
|
||||
|
||||
jmethodID GetDisplayAlertPrompt() {
|
||||
return s_display_alert_prompt;
|
||||
}
|
||||
|
||||
jmethodID GetAlertPromptButton() {
|
||||
return s_alert_prompt_button;
|
||||
}
|
||||
|
||||
jmethodID GetIsPortraitMode() {
|
||||
return s_is_portrait_mode;
|
||||
}
|
||||
@ -85,6 +95,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
||||
s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Z)Z");
|
||||
s_display_alert_prompt =
|
||||
env->GetStaticMethodID(s_native_library_class, "displayAlertPrompt",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
|
||||
s_alert_prompt_button =
|
||||
env->GetStaticMethodID(s_native_library_class, "alertPromptButton", "()I");
|
||||
s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z");
|
||||
s_landscape_screen_layout =
|
||||
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I");
|
||||
|
@ -11,6 +11,8 @@ namespace IDCache {
|
||||
JNIEnv* GetEnvForThread();
|
||||
jclass GetNativeLibraryClass();
|
||||
jmethodID GetDisplayAlertMsg();
|
||||
jmethodID GetDisplayAlertPrompt();
|
||||
jmethodID GetAlertPromptButton();
|
||||
jmethodID GetIsPortraitMode();
|
||||
jmethodID GetLandscapeScreenLayout();
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
@ -53,6 +57,58 @@ std::condition_variable running_cv;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
static std::string GetJString(JNIEnv* env, jstring jstr) {
|
||||
if (!jstr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* s = env->GetStringUTFChars(jstr, nullptr);
|
||||
std::string result = s;
|
||||
env->ReleaseStringUTFChars(jstr, s);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool DisplayAlertMessage(const char* caption, const char* text, bool yes_no) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
||||
// Execute the Java method.
|
||||
jboolean result = env->CallStaticBooleanMethod(
|
||||
IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), env->NewStringUTF(caption),
|
||||
env->NewStringUTF(text), yes_no ? JNI_TRUE : JNI_FALSE);
|
||||
|
||||
return result != JNI_FALSE;
|
||||
}
|
||||
|
||||
static std::string DisplayAlertPrompt(const char* caption, const char* text, int buttonConfig) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
||||
jstring value = reinterpret_cast<jstring>(env->CallStaticObjectMethod(
|
||||
IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertPrompt(),
|
||||
env->NewStringUTF(caption), env->NewStringUTF(text), buttonConfig));
|
||||
|
||||
return GetJString(env, value);
|
||||
}
|
||||
|
||||
static int AlertPromptButton() {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
||||
// Execute the Java method.
|
||||
return static_cast<int>(env->CallStaticIntMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetAlertPromptButton()));
|
||||
}
|
||||
|
||||
class AndroidKeyboard final : public Frontend::SoftwareKeyboard {
|
||||
public:
|
||||
void Execute(const Frontend::KeyboardConfig& config) override {
|
||||
SoftwareKeyboard::Execute(config);
|
||||
Finalize(DisplayAlertPrompt("Enter text", config.hint_text.c_str(),
|
||||
static_cast<int>(this->config.button_config)),
|
||||
AlertPromptButton());
|
||||
}
|
||||
|
||||
void ShowError(const std::string& error) override {}
|
||||
};
|
||||
|
||||
static int RunCitra(const std::string& filepath) {
|
||||
// Citra core only supports a single running instance
|
||||
std::lock_guard<std::mutex> lock(running_mutex);
|
||||
@ -72,7 +128,7 @@ static int RunCitra(const std::string& filepath) {
|
||||
|
||||
// Register frontend applets
|
||||
Frontend::RegisterDefaultApplets();
|
||||
system.RegisterSoftwareKeyboard(std::make_shared<AndroidKeyboard>(env));
|
||||
system.RegisterSoftwareKeyboard(std::make_shared<AndroidKeyboard>());
|
||||
|
||||
{
|
||||
// Forces a config reload on game boot, if the user changed settings in the UI
|
||||
@ -139,17 +195,6 @@ static int RunCitra(const std::string& filepath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::string GetJString(JNIEnv* env, jstring jstr) {
|
||||
if (!jstr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* s = env->GetStringUTFChars(jstr, nullptr);
|
||||
std::string result = s;
|
||||
env->ReleaseStringUTFChars(jstr, s);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1android_NativeLibrary_SurfaceChanged(JNIEnv* env, jobject obj,
|
||||
jobject surf) {
|
||||
s_surf = ANativeWindow_fromSurface(env, surf);
|
||||
@ -250,7 +295,8 @@ jintArray Java_org_citra_citra_1android_NativeLibrary_GetBanner(JNIEnv* env, job
|
||||
}
|
||||
|
||||
jintArray Banner = env->NewIntArray(icon_data.size());
|
||||
env->SetIntArrayRegion(Banner, 0, env->GetArrayLength(Banner), reinterpret_cast<jint*>(icon_data.data()));
|
||||
env->SetIntArrayRegion(Banner, 0, env->GetArrayLength(Banner),
|
||||
reinterpret_cast<jint*>(icon_data.data()));
|
||||
|
||||
return Banner;
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Initialise and run the emulator
|
||||
static int RunCitra(const std::string& path);
|
||||
#pragma once
|
||||
|
||||
// Function calls from the Java side
|
||||
#ifdef __cplusplus
|
||||
|
@ -8,4 +8,6 @@
|
||||
<dimen name="spacing_small">4dp</dimen>
|
||||
<dimen name="spacing_medlarge">12dp</dimen>
|
||||
<dimen name="spacing_large">16dp</dimen>
|
||||
|
||||
<dimen name="dialog_margin">20dp</dimen>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user