mirror of
https://github.com/SSimco/Cemu.git
synced 2024-11-23 05:19:40 +00:00
Updated swkbd to use native android input
This commit is contained in:
parent
e8ebeab313
commit
bdd6b3f835
@ -983,7 +983,9 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||
g_renderer->HandleScreenshotRequest(textureView, isPadView);
|
||||
if (!g_renderer->ImguiBegin(!isPadView))
|
||||
return;
|
||||
swkbd_render(!isPadView);
|
||||
#if !__ANDROID__
|
||||
swkbd::render(!isPadView);
|
||||
#endif
|
||||
nn::erreula::render(!isPadView);
|
||||
LatteOverlay_render(isPadView);
|
||||
g_renderer->ImguiEnd();
|
||||
@ -1003,7 +1005,7 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
|
||||
const bool tabPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::TAB);
|
||||
const bool ctrlPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::LCONTROL);
|
||||
|
||||
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed;
|
||||
bool showDRC = swkbd::hasKeyboardInputHook() == false && tabPressed;
|
||||
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC;
|
||||
|
||||
if (ctrlPressed && tabPressed)
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "OS/libs/swkbd/swkbd.h"
|
||||
#include "Cafe/OS/common/OSCommon.h"
|
||||
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
||||
#include "Cafe/OS/libs/gx2/GX2.h"
|
||||
@ -91,6 +92,13 @@ typedef struct
|
||||
|
||||
swkbdInternalState_t* swkbdInternalState = NULL;
|
||||
|
||||
std::shared_ptr<swkbd::swkbdCallbacks> s_swkbdCallbacks;
|
||||
|
||||
void swkbd::setSwkbdCallbacks(const std::shared_ptr<swkbd::swkbdCallbacks>& swkbdCallbacks)
|
||||
{
|
||||
s_swkbdCallbacks = swkbdCallbacks;
|
||||
}
|
||||
|
||||
void swkbdExport_SwkbdCreate(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "swkbd.SwkbdCreate(0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]);
|
||||
@ -235,6 +243,9 @@ void swkbdExport_SwkbdAppearInputForm(PPCInterpreter_t* hCPU)
|
||||
swkbdInternalState->formStringBuffer[0] = '\0';
|
||||
swkbdInternalState->formStringLength = 0;
|
||||
}
|
||||
if (s_swkbdCallbacks)
|
||||
s_swkbdCallbacks->showSoftwareKeyboard({swkbdInternalState->formStringBuffer, swkbdInternalState->formStringBuffer + swkbdInternalState->formStringLength}, swkbdInternalState->maxTextLength);
|
||||
|
||||
osLib_returnFromFunction(hCPU, 1);
|
||||
}
|
||||
|
||||
@ -257,6 +268,11 @@ void swkbdExport_SwkbdAppearKeyboard(PPCInterpreter_t* hCPU)
|
||||
swkbdInternalState->formStringBuffer[0] = '\0';
|
||||
swkbdInternalState->formStringLength = 0;
|
||||
swkbdInternalState->keyboardArg = *keyboardArg;
|
||||
if (s_swkbdCallbacks)
|
||||
{
|
||||
sint32 maxLength = std::max(swkbdInternalState->keyboardArg.receiverArg.stringBufSize - 1, 0);
|
||||
s_swkbdCallbacks->showSoftwareKeyboard({swkbdInternalState->formStringBuffer, swkbdInternalState->formStringBuffer + swkbdInternalState->formStringLength}, maxLength);
|
||||
}
|
||||
osLib_returnFromFunction(hCPU, 1);
|
||||
}
|
||||
|
||||
@ -264,6 +280,8 @@ void swkbdExport_SwkbdDisappearInputForm(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
debug_printf("SwkbdDisappearInputForm__3RplFv LR: %08x\n", hCPU->spr.LR);
|
||||
swkbdInternalState->isActive = false;
|
||||
if (s_swkbdCallbacks)
|
||||
s_swkbdCallbacks->hideSoftwareKeyboard();
|
||||
osLib_returnFromFunction(hCPU, 1);
|
||||
}
|
||||
|
||||
@ -271,6 +289,8 @@ void swkbdExport_SwkbdDisappearKeyboard(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
debug_printf("SwkbdDisappearKeyboard__3RplFv LR: %08x\n", hCPU->spr.LR);
|
||||
swkbdInternalState->isActive = false;
|
||||
if (s_swkbdCallbacks)
|
||||
s_swkbdCallbacks->hideSoftwareKeyboard();
|
||||
osLib_returnFromFunction(hCPU, 1);
|
||||
}
|
||||
|
||||
@ -349,8 +369,7 @@ void swkbdExport_SwkbdIsNeedCalcSubThreadPredict(PPCInterpreter_t* hCPU)
|
||||
osLib_returnFromFunction(hCPU, r?1:0);
|
||||
}
|
||||
|
||||
void swkbd_keyInput(uint32 keyCode);
|
||||
void swkbd_render(bool mainWindow)
|
||||
void swkbd::render(bool mainWindow)
|
||||
{
|
||||
// only render if active
|
||||
if( swkbdInternalState == NULL || swkbdInternalState->isActive == false)
|
||||
@ -438,13 +457,13 @@ void swkbd_render(bool mainWindow)
|
||||
if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0}))
|
||||
{
|
||||
if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0)
|
||||
swkbd_keyInput(8);
|
||||
swkbd::keyInput(BACKSPACE_KEYCODE);
|
||||
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0)
|
||||
swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated;
|
||||
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0)
|
||||
swkbd_keyInput(13);
|
||||
swkbd::keyInput(RETURN_KEYCODE);
|
||||
else
|
||||
swkbd_keyInput(*key);
|
||||
swkbd::keyInput(*key);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@ -470,13 +489,13 @@ void swkbd_render(bool mainWindow)
|
||||
if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0 }))
|
||||
{
|
||||
if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0)
|
||||
swkbd_keyInput(8);
|
||||
swkbd::keyInput(BACKSPACE_KEYCODE);
|
||||
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0)
|
||||
swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated;
|
||||
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0)
|
||||
swkbd_keyInput(13);
|
||||
swkbd::keyInput(RETURN_KEYCODE);
|
||||
else
|
||||
swkbd_keyInput(*key);
|
||||
swkbd::keyInput(*key);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@ -492,7 +511,7 @@ void swkbd_render(bool mainWindow)
|
||||
if (io.NavInputs[ImGuiNavInput_Cancel] > 0)
|
||||
{
|
||||
if(!swkbdInternalState->cancelState)
|
||||
swkbd_keyInput(8); // backspace
|
||||
swkbd::keyInput(BACKSPACE_KEYCODE);
|
||||
swkbdInternalState->cancelState = true;
|
||||
}
|
||||
else
|
||||
@ -501,7 +520,7 @@ void swkbd_render(bool mainWindow)
|
||||
if (io.NavInputs[ImGuiNavInput_Input] > 0)
|
||||
{
|
||||
if (!swkbdInternalState->returnState)
|
||||
swkbd_keyInput(13); // return
|
||||
swkbd::keyInput(RETURN_KEYCODE);
|
||||
swkbdInternalState->returnState = true;
|
||||
}
|
||||
else
|
||||
@ -511,7 +530,7 @@ void swkbd_render(bool mainWindow)
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
bool swkbd_hasKeyboardInputHook()
|
||||
bool swkbd::hasKeyboardInputHook()
|
||||
{
|
||||
return swkbdInternalState != NULL && swkbdInternalState->isActive;
|
||||
}
|
||||
@ -564,9 +583,9 @@ void swkbd_inputStringChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void swkbd_keyInput(uint32 keyCode)
|
||||
void swkbd::keyInput(uint32 keyCode)
|
||||
{
|
||||
if (keyCode == 8 || keyCode == 127) // backspace || backwards delete
|
||||
if (keyCode == BACKSPACE_KEYCODE || keyCode == BACKWARDS_DELETE_KEYCODE)
|
||||
{
|
||||
if (swkbdInternalState->formStringLength > 0)
|
||||
swkbdInternalState->formStringLength--;
|
||||
@ -574,7 +593,7 @@ void swkbd_keyInput(uint32 keyCode)
|
||||
swkbd_inputStringChanged();
|
||||
return;
|
||||
}
|
||||
else if (keyCode == 13) // return
|
||||
else if (keyCode == RETURN_KEYCODE)
|
||||
{
|
||||
swkbd_finishInput();
|
||||
return;
|
||||
@ -593,7 +612,7 @@ void swkbd_keyInput(uint32 keyCode)
|
||||
{
|
||||
// allowed
|
||||
}
|
||||
else if (keyCode < 32)
|
||||
else if (keyCode < CONTROL_KEYCODE)
|
||||
{
|
||||
// control key
|
||||
return;
|
||||
|
@ -1,8 +1,24 @@
|
||||
void swkbd_render(bool mainWindow);
|
||||
bool swkbd_hasKeyboardInputHook();
|
||||
void swkbd_keyInput(uint32 keyCode);
|
||||
#pragma once
|
||||
|
||||
namespace swkbd
|
||||
{
|
||||
constexpr uint32 BACKSPACE_KEYCODE = 8;
|
||||
constexpr uint32 BACKWARDS_DELETE_KEYCODE = 127;
|
||||
constexpr uint32 RETURN_KEYCODE = 13;
|
||||
constexpr uint32 CONTROL_KEYCODE = 32;
|
||||
|
||||
class swkbdCallbacks
|
||||
{
|
||||
public:
|
||||
virtual void showSoftwareKeyboard(const std::string& initialText, sint32 maxLength) = 0;
|
||||
virtual void hideSoftwareKeyboard() = 0;
|
||||
};
|
||||
|
||||
void setSwkbdCallbacks(const std::shared_ptr<swkbdCallbacks>& callbacks);
|
||||
|
||||
void render(bool mainWindow);
|
||||
bool hasKeyboardInputHook();
|
||||
void keyInput(uint32 keyCode);
|
||||
void load();
|
||||
}
|
||||
|
||||
} // namespace swkbd
|
||||
|
27
src/android/app/src/main/cpp/AndroidSwkbdCallbacks.cpp
Normal file
27
src/android/app/src/main/cpp/AndroidSwkbdCallbacks.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include "AndroidSwkbdCallbacks.h"
|
||||
#include "JNIUtils.h"
|
||||
|
||||
AndroidSwkbdCallbacks::AndroidSwkbdCallbacks()
|
||||
{
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
m_emulationActivityClass = JNIUtils::Scopedjclass("info/cemu/Cemu/emulation/EmulationActivity");
|
||||
m_showSoftwareKeyboardMethodID = env->GetStaticMethodID(*m_emulationActivityClass, "showEmulationTextInput", "(Ljava/lang/String;I)V");
|
||||
m_hideSoftwareKeyboardMethodID = env->GetStaticMethodID(*m_emulationActivityClass, "hideEmulationTextInput", "()V");
|
||||
}
|
||||
|
||||
void AndroidSwkbdCallbacks::showSoftwareKeyboard(const std::string& initialText, sint32 maxLength)
|
||||
{
|
||||
std::thread([&, this]() {
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
jstring j_initialText = env->NewStringUTF(initialText.c_str());
|
||||
JNIUtils::ScopedJNIENV()->CallStaticVoidMethod(*m_emulationActivityClass, m_showSoftwareKeyboardMethodID, j_initialText, maxLength);
|
||||
env->DeleteLocalRef(j_initialText);
|
||||
}).join();
|
||||
}
|
||||
|
||||
void AndroidSwkbdCallbacks::hideSoftwareKeyboard()
|
||||
{
|
||||
std::thread([this]() {
|
||||
JNIUtils::ScopedJNIENV()->CallStaticVoidMethod(*m_emulationActivityClass, m_hideSoftwareKeyboardMethodID);
|
||||
}).join();
|
||||
}
|
15
src/android/app/src/main/cpp/AndroidSwkbdCallbacks.h
Normal file
15
src/android/app/src/main/cpp/AndroidSwkbdCallbacks.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cafe/OS/libs/swkbd/swkbd.h"
|
||||
#include "JNIUtils.h"
|
||||
class AndroidSwkbdCallbacks : public swkbd::swkbdCallbacks
|
||||
{
|
||||
JNIUtils::Scopedjclass m_emulationActivityClass;
|
||||
jmethodID m_showSoftwareKeyboardMethodID;
|
||||
jmethodID m_hideSoftwareKeyboardMethodID;
|
||||
|
||||
public:
|
||||
AndroidSwkbdCallbacks();
|
||||
void showSoftwareKeyboard(const std::string& initialText, sint32 maxLength) override;
|
||||
void hideSoftwareKeyboard() override;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
add_library(CemuAndroid SHARED
|
||||
AndroidAudio.cpp
|
||||
AndroidEmulatedController.cpp
|
||||
AndroidSwkbdCallbacks.cpp
|
||||
CMakeLists.txt
|
||||
GameTitleLoader.cpp
|
||||
Image.cpp
|
||||
@ -11,6 +12,7 @@ add_library(CemuAndroid SHARED
|
||||
NativeInput.cpp
|
||||
NativeLib.cpp
|
||||
NativeSettings.cpp
|
||||
NativeSwkbd.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(CemuAndroid PRIVATE
|
||||
|
@ -7,6 +7,8 @@ namespace JNIUtils
|
||||
|
||||
std::string JNIUtils::JStringToString(JNIEnv* env, jstring jstr)
|
||||
{
|
||||
if (jstr == nullptr)
|
||||
return {};
|
||||
const char* c_str = env->GetStringUTFChars(jstr, nullptr);
|
||||
std::string str(c_str);
|
||||
env->ReleaseStringUTFChars(jstr, c_str);
|
||||
|
56
src/android/app/src/main/cpp/NativeSwkbd.cpp
Normal file
56
src/android/app/src/main/cpp/NativeSwkbd.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "JNIUtils.h"
|
||||
|
||||
#include "Cafe/OS/libs/swkbd/swkbd.h"
|
||||
#include "AndroidSwkbdCallbacks.h"
|
||||
|
||||
namespace NativeSwkbd
|
||||
{
|
||||
std::shared_ptr<swkbd::swkbdCallbacks> s_swkbdCallbacks;
|
||||
std::string s_currentInputText;
|
||||
struct StrDiffs
|
||||
{
|
||||
size_t newTextStartIndex;
|
||||
size_t numberOfCharacterToDelete;
|
||||
};
|
||||
StrDiffs getStringDiffs(const std::string& newText, const std::string& currentText)
|
||||
{
|
||||
if (newText.length() < currentText.length() && currentText.starts_with(newText))
|
||||
return {newText.length(), currentText.length() - newText.length()};
|
||||
if (newText.length() >= currentText.length() && newText.starts_with(currentText))
|
||||
return {currentText.length(), 0};
|
||||
return {0, currentText.length()};
|
||||
}
|
||||
} // namespace NativeSwkbd
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_initializeSwkbd([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
if (NativeSwkbd::s_swkbdCallbacks != nullptr)
|
||||
return;
|
||||
NativeSwkbd::s_swkbdCallbacks = std::make_shared<AndroidSwkbdCallbacks>();
|
||||
swkbd::setSwkbdCallbacks(NativeSwkbd::s_swkbdCallbacks);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_setCurrentInputText([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring initial_text)
|
||||
{
|
||||
NativeSwkbd::s_currentInputText = JNIUtils::JStringToString(env, initial_text);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_onTextChanged([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_text)
|
||||
{
|
||||
std::string text = JNIUtils::JStringToString(env, j_text);
|
||||
auto stringDiff = NativeSwkbd::getStringDiffs(text, NativeSwkbd::s_currentInputText);
|
||||
for (size_t i = 0; i < stringDiff.numberOfCharacterToDelete; i++)
|
||||
swkbd::keyInput(swkbd::BACKSPACE_KEYCODE);
|
||||
for (size_t i = stringDiff.newTextStartIndex; i < text.length(); i++)
|
||||
swkbd::keyInput(text.at(i));
|
||||
NativeSwkbd::s_currentInputText = std::move(text);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_onFinishedInputEdit([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
swkbd::keyInput(swkbd::RETURN_KEYCODE);
|
||||
}
|
@ -7,6 +7,7 @@ import java.io.File;
|
||||
|
||||
import info.cemu.Cemu.nativeinterface.NativeEmulation;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGraphicPacks;
|
||||
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
|
||||
|
||||
public class CemuApplication extends Application {
|
||||
static {
|
||||
@ -37,6 +38,7 @@ public class CemuApplication extends Application {
|
||||
NativeEmulation.setDPI(displayMetrics.density);
|
||||
NativeEmulation.initializeActiveSettings(getInternalFolder().toString(), getInternalFolder().toString());
|
||||
NativeEmulation.initializeEmulation();
|
||||
NativeSwkbd.initializeSwkbd();
|
||||
NativeGraphicPacks.refreshGraphicPacks();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
package info.cemu.Cemu.emulation;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputFilter;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
@ -16,17 +19,59 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import info.cemu.Cemu.BuildConfig;
|
||||
import info.cemu.Cemu.R;
|
||||
import info.cemu.Cemu.databinding.ActivityEmulationBinding;
|
||||
import info.cemu.Cemu.input.InputManager;
|
||||
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
|
||||
|
||||
public class EmulationActivity extends AppCompatActivity implements Observer<EmulationData> {
|
||||
private EmulationViewModel viewModel;
|
||||
private boolean hasEmulationError;
|
||||
public static final String LAUNCH_PATH = "LAUNCH_PATH";
|
||||
private ActivityEmulationBinding binding;
|
||||
private EmulationFragment emulationFragment;
|
||||
public static final String EXTRA_LAUNCH_PATH = BuildConfig.APPLICATION_ID + ".LaunchPath";
|
||||
private final InputManager inputManager = new InputManager();
|
||||
private static EmulationActivity emulationActivityInstance;
|
||||
private AlertDialog emulationTextInputDialog;
|
||||
|
||||
/**
|
||||
* This method is called by swkbd using JNI.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void showEmulationTextInput(String initialText, int maxLength) {
|
||||
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog != null)
|
||||
return;
|
||||
NativeSwkbd.setCurrentInputText(initialText);
|
||||
emulationActivityInstance.runOnUiThread(() -> {
|
||||
var inputEditTextLayout = emulationActivityInstance.getLayoutInflater().inflate(R.layout.layout_emulation_input, null);
|
||||
EmulationTextInputEditText inputEditText = inputEditTextLayout.requireViewById(R.id.emulation_input_text);
|
||||
inputEditText.updateText(initialText);
|
||||
var dialog = new MaterialAlertDialogBuilder(emulationActivityInstance)
|
||||
.setView(inputEditTextLayout)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.done, (d, w) -> {
|
||||
}).show();
|
||||
var doneButton = Objects.requireNonNull(dialog.getButton(DialogInterface.BUTTON_POSITIVE));
|
||||
doneButton.setEnabled(false);
|
||||
doneButton.setOnClickListener(v -> inputEditText.onFinishedEdit());
|
||||
inputEditText.setOnTextChangedListener(s -> doneButton.setEnabled(s.length() > 0));
|
||||
if (maxLength > 0)
|
||||
inputEditText.appendFilter(new InputFilter.LengthFilter(maxLength));
|
||||
emulationActivityInstance.emulationTextInputDialog = dialog;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by swkbd using JNI.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void hideEmulationTextInput() {
|
||||
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog == null)
|
||||
return;
|
||||
var textInputDialog = emulationActivityInstance.emulationTextInputDialog;
|
||||
emulationActivityInstance.emulationTextInputDialog = null;
|
||||
emulationActivityInstance.runOnUiThread(textInputDialog::dismiss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
@ -45,6 +90,7 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
emulationActivityInstance = this;
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
@ -52,14 +98,14 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
|
||||
}
|
||||
});
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(EmulationViewModel.class);
|
||||
EmulationViewModel viewModel = new ViewModelProvider(this).get(EmulationViewModel.class);
|
||||
viewModel.getEmulationData().observe(this, this);
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
Uri data = intent.getData();
|
||||
String launchPath = null;
|
||||
if (extras != null) {
|
||||
launchPath = extras.getString(LAUNCH_PATH);
|
||||
launchPath = extras.getString(EXTRA_LAUNCH_PATH);
|
||||
}
|
||||
if (launchPath == null && data != null) {
|
||||
launchPath = data.toString();
|
||||
@ -68,9 +114,9 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
|
||||
throw new RuntimeException("launchPath is null");
|
||||
}
|
||||
setFullscreen();
|
||||
binding = ActivityEmulationBinding.inflate(getLayoutInflater());
|
||||
info.cemu.Cemu.databinding.ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
|
||||
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
|
||||
if (emulationFragment == null) {
|
||||
emulationFragment = new EmulationFragment(launchPath);
|
||||
getSupportFragmentManager()
|
||||
|
@ -0,0 +1,96 @@
|
||||
package info.cemu.Cemu.emulation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
|
||||
|
||||
public class EmulationTextInputEditText extends TextInputEditText {
|
||||
static private final Pattern INPUT_PATTERN = Pattern.compile("^[\\da-zA-Z \\-/;:',.?!#\\[\\]$%^&*()_@\\\\<>+=]+$");
|
||||
|
||||
public EmulationTextInputEditText(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public EmulationTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, com.google.android.material.R.attr.editTextStyle);
|
||||
}
|
||||
|
||||
public void appendFilter(InputFilter inputFilter) {
|
||||
setFilters(Stream.concat(Arrays.stream(getFilters()), Stream.of(inputFilter)).toArray(InputFilter[]::new));
|
||||
}
|
||||
|
||||
public void updateText(String text) {
|
||||
boolean hasFocus = hasFocus();
|
||||
if (hasFocus)
|
||||
clearFocus();
|
||||
setText(text);
|
||||
if (hasFocus)
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
public EmulationTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
appendFilter((source, start, end, dest, dstart, dend) -> {
|
||||
if (INPUT_PATTERN.matcher(source).matches())
|
||||
return null;
|
||||
return "";
|
||||
});
|
||||
setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_NORMAL);
|
||||
addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence text, int start, int before, int count) {
|
||||
if (!hasFocus())
|
||||
return;
|
||||
if (onTextChangedListener != null)
|
||||
onTextChangedListener.onTextChanged(text);
|
||||
NativeSwkbd.onTextChanged(text.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnTextChangedListener {
|
||||
void onTextChanged(CharSequence text);
|
||||
}
|
||||
|
||||
|
||||
private OnTextChangedListener onTextChangedListener;
|
||||
|
||||
@Override
|
||||
public void onEditorAction(int actionCode) {
|
||||
var text = getText();
|
||||
if (actionCode == EditorInfo.IME_ACTION_DONE && text != null && text.length() > 0) {
|
||||
onFinishedEdit();
|
||||
}
|
||||
super.onEditorAction(actionCode);
|
||||
}
|
||||
|
||||
public void onFinishedEdit() {
|
||||
NativeSwkbd.onFinishedInputEdit();
|
||||
}
|
||||
|
||||
public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
|
||||
this.onTextChangedListener = onTextChangedListener;
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ public class GamesFragment extends Fragment {
|
||||
currentGamePaths = new HashSet<>(NativeSettings.getGamesPaths());
|
||||
gameAdapter = new GameAdapter(game -> {
|
||||
Intent intent = new Intent(getContext(), EmulationActivity.class);
|
||||
intent.putExtra(EmulationActivity.LAUNCH_PATH, game.path());
|
||||
intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path());
|
||||
startActivity(intent);
|
||||
});
|
||||
gameListViewModel = new ViewModelProvider(this).get(GameListViewModel.class);
|
||||
@ -74,6 +74,8 @@ public class GamesFragment extends Fragment {
|
||||
MenuInflater inflater = requireActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.game, menu);
|
||||
Game selectedGame = gameAdapter.getSelectedGame();
|
||||
if (selectedGame == null)
|
||||
return;
|
||||
menu.findItem(R.id.favorite).setChecked(selectedGame.isFavorite());
|
||||
menu.findItem(R.id.remove_shader_caches).setEnabled(NativeGameTitles.titleHasShaderCacheFiles(selectedGame.titleId()));
|
||||
}
|
||||
@ -161,7 +163,6 @@ public class GamesFragment extends Fragment {
|
||||
});
|
||||
recyclerView.setAdapter(gameAdapter);
|
||||
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
package info.cemu.Cemu.nativeinterface;
|
||||
|
||||
public class NativeSwkbd {
|
||||
public static native void initializeSwkbd();
|
||||
|
||||
public static native void setCurrentInputText(String text);
|
||||
|
||||
public static native void onTextChanged(String text);
|
||||
|
||||
public static native void onFinishedInputEdit();
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<info.cemu.Cemu.emulation.EmulationTextInputEditText
|
||||
android:id="@+id/emulation_input_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/emulation_input_text_hint" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
@ -238,4 +238,6 @@
|
||||
<string name="console_language_russian">Russian</string>
|
||||
<string name="console_language_taiwanese">Taiwanese</string>
|
||||
<string name="console_language">Console language</string>
|
||||
<string name="emulation_input_text_hint">Input text</string>
|
||||
<string name="done">Done</string>
|
||||
</resources>
|
@ -1613,7 +1613,7 @@ void MainWindow::OnKeyUp(wxKeyEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
if (swkbd::hasKeyboardInputHook())
|
||||
return;
|
||||
|
||||
const auto code = event.GetKeyCode();
|
||||
@ -1640,8 +1640,8 @@ void MainWindow::OnKeyDown(wxKeyEvent& event)
|
||||
|
||||
void MainWindow::OnChar(wxKeyEvent& event)
|
||||
{
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
swkbd_keyInput(event.GetUnicodeKey());
|
||||
if (swkbd::hasKeyboardInputHook())
|
||||
swkbd::keyInput(event.GetUnicodeKey());
|
||||
|
||||
// event.Skip();
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ void PadViewFrame::OnKeyUp(wxKeyEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
if (swkbd::hasKeyboardInputHook())
|
||||
return;
|
||||
|
||||
const auto code = event.GetKeyCode();
|
||||
@ -173,8 +173,8 @@ void PadViewFrame::OnGesturePan(wxPanGestureEvent& event)
|
||||
|
||||
void PadViewFrame::OnChar(wxKeyEvent& event)
|
||||
{
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
swkbd_keyInput(event.GetUnicodeKey());
|
||||
if (swkbd::hasKeyboardInputHook())
|
||||
swkbd::keyInput(event.GetUnicodeKey());
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user