diff options
Diffstat (limited to 'src')
6 files changed, 255 insertions, 279 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 8304c2aa5..3589e7629 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
| @@ -83,22 +83,22 @@ open class EmulationActivity : AppCompatActivity() { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { | 85 | override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { |
| 86 | if (event.action == android.view.KeyEvent.ACTION_DOWN) { | 86 | if (event.action == KeyEvent.ACTION_DOWN) { |
| 87 | if (keyCode == android.view.KeyEvent.KEYCODE_ENTER) { | 87 | if (keyCode == KeyEvent.KEYCODE_ENTER) { |
| 88 | // Special case, we do not support multiline input, dismiss the keyboard. | 88 | // Special case, we do not support multiline input, dismiss the keyboard. |
| 89 | val overlayView: View = | 89 | val overlayView: View = |
| 90 | this.findViewById<View>(R.id.surface_input_overlay) | 90 | this.findViewById(R.id.surface_input_overlay) |
| 91 | val im = | 91 | val im = |
| 92 | overlayView.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager | 92 | overlayView.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager |
| 93 | im.hideSoftInputFromWindow(overlayView.windowToken, 0); | 93 | im.hideSoftInputFromWindow(overlayView.windowToken, 0) |
| 94 | } else { | 94 | } else { |
| 95 | val textChar = event.getUnicodeChar(); | 95 | val textChar = event.unicodeChar |
| 96 | if (textChar == 0) { | 96 | if (textChar == 0) { |
| 97 | // No text, button input. | 97 | // No text, button input. |
| 98 | NativeLibrary.SubmitInlineKeyboardInput(keyCode); | 98 | NativeLibrary.SubmitInlineKeyboardInput(keyCode) |
| 99 | } else { | 99 | } else { |
| 100 | // Text submitted. | 100 | // Text submitted. |
| 101 | NativeLibrary.SubmitInlineKeyboardText(textChar.toChar().toString()); | 101 | NativeLibrary.SubmitInlineKeyboardText(textChar.toChar().toString()) |
| 102 | } | 102 | } |
| 103 | } | 103 | } |
| 104 | } | 104 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java deleted file mode 100644 index 8ad4b1e22..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java +++ /dev/null | |||
| @@ -1,264 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.applets; | ||
| 5 | |||
| 6 | import android.app.Activity; | ||
| 7 | import android.app.Dialog; | ||
| 8 | import android.content.Context; | ||
| 9 | import android.content.DialogInterface; | ||
| 10 | import android.graphics.Rect; | ||
| 11 | import android.os.Bundle; | ||
| 12 | import android.os.Handler; | ||
| 13 | import android.os.ResultReceiver; | ||
| 14 | import android.text.InputFilter; | ||
| 15 | import android.text.InputType; | ||
| 16 | import android.view.ViewGroup; | ||
| 17 | import android.view.ViewTreeObserver; | ||
| 18 | import android.view.WindowInsets; | ||
| 19 | import android.view.inputmethod.InputMethodManager; | ||
| 20 | import android.widget.EditText; | ||
| 21 | import android.widget.FrameLayout; | ||
| 22 | |||
| 23 | import androidx.annotation.NonNull; | ||
| 24 | import androidx.appcompat.app.AlertDialog; | ||
| 25 | import androidx.core.view.ViewCompat; | ||
| 26 | import androidx.fragment.app.DialogFragment; | ||
| 27 | |||
| 28 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||
| 29 | |||
| 30 | import org.yuzu.yuzu_emu.YuzuApplication; | ||
| 31 | import org.yuzu.yuzu_emu.NativeLibrary; | ||
| 32 | import org.yuzu.yuzu_emu.R; | ||
| 33 | import org.yuzu.yuzu_emu.activities.EmulationActivity; | ||
| 34 | |||
| 35 | import java.util.Objects; | ||
| 36 | |||
| 37 | public final class SoftwareKeyboard { | ||
| 38 | /// Corresponds to Service::AM::Applets::SwkbdType | ||
| 39 | private interface SwkbdType { | ||
| 40 | int Normal = 0; | ||
| 41 | int NumberPad = 1; | ||
| 42 | int Qwerty = 2; | ||
| 43 | int Unknown3 = 3; | ||
| 44 | int Latin = 4; | ||
| 45 | int SimplifiedChinese = 5; | ||
| 46 | int TraditionalChinese = 6; | ||
| 47 | int Korean = 7; | ||
| 48 | }; | ||
| 49 | |||
| 50 | /// Corresponds to Service::AM::Applets::SwkbdPasswordMode | ||
| 51 | private interface SwkbdPasswordMode { | ||
| 52 | int Disabled = 0; | ||
| 53 | int Enabled = 1; | ||
| 54 | }; | ||
| 55 | |||
| 56 | /// Corresponds to Service::AM::Applets::SwkbdResult | ||
| 57 | private interface SwkbdResult { | ||
| 58 | int Ok = 0; | ||
| 59 | int Cancel = 1; | ||
| 60 | }; | ||
| 61 | |||
| 62 | public static class KeyboardConfig implements java.io.Serializable { | ||
| 63 | public String ok_text; | ||
| 64 | public String header_text; | ||
| 65 | public String sub_text; | ||
| 66 | public String guide_text; | ||
| 67 | public String initial_text; | ||
| 68 | public short left_optional_symbol_key; | ||
| 69 | public short right_optional_symbol_key; | ||
| 70 | public int max_text_length; | ||
| 71 | public int min_text_length; | ||
| 72 | public int initial_cursor_position; | ||
| 73 | public int type; | ||
| 74 | public int password_mode; | ||
| 75 | public int text_draw_type; | ||
| 76 | public int key_disable_flags; | ||
| 77 | public boolean use_blur_background; | ||
| 78 | public boolean enable_backspace_button; | ||
| 79 | public boolean enable_return_button; | ||
| 80 | public boolean disable_cancel_button; | ||
| 81 | } | ||
| 82 | |||
| 83 | /// Corresponds to Frontend::KeyboardData | ||
| 84 | public static class KeyboardData { | ||
| 85 | public int result; | ||
| 86 | public String text; | ||
| 87 | |||
| 88 | private KeyboardData(int result, String text) { | ||
| 89 | this.result = result; | ||
| 90 | this.text = text; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | public static class KeyboardDialogFragment extends DialogFragment { | ||
| 95 | static KeyboardDialogFragment newInstance(KeyboardConfig config) { | ||
| 96 | KeyboardDialogFragment frag = new KeyboardDialogFragment(); | ||
| 97 | Bundle args = new Bundle(); | ||
| 98 | args.putSerializable("config", config); | ||
| 99 | frag.setArguments(args); | ||
| 100 | return frag; | ||
| 101 | } | ||
| 102 | |||
| 103 | @NonNull | ||
| 104 | @Override | ||
| 105 | public Dialog onCreateDialog(Bundle savedInstanceState) { | ||
| 106 | final Activity emulationActivity = getActivity(); | ||
| 107 | assert emulationActivity != null; | ||
| 108 | |||
| 109 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( | ||
| 110 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); | ||
| 111 | params.leftMargin = params.rightMargin = | ||
| 112 | YuzuApplication.getAppContext().getResources().getDimensionPixelSize( | ||
| 113 | R.dimen.dialog_margin); | ||
| 114 | |||
| 115 | KeyboardConfig config = Objects.requireNonNull( | ||
| 116 | (KeyboardConfig) requireArguments().getSerializable("config")); | ||
| 117 | |||
| 118 | // Set up the input | ||
| 119 | EditText editText = new EditText(YuzuApplication.getAppContext()); | ||
| 120 | editText.setHint(config.initial_text); | ||
| 121 | editText.setSingleLine(!config.enable_return_button); | ||
| 122 | editText.setLayoutParams(params); | ||
| 123 | editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(config.max_text_length)}); | ||
| 124 | |||
| 125 | // Handle input type | ||
| 126 | int input_type = 0; | ||
| 127 | switch (config.type) | ||
| 128 | { | ||
| 129 | case SwkbdType.Normal: | ||
| 130 | case SwkbdType.Qwerty: | ||
| 131 | case SwkbdType.Unknown3: | ||
| 132 | case SwkbdType.Latin: | ||
| 133 | case SwkbdType.SimplifiedChinese: | ||
| 134 | case SwkbdType.TraditionalChinese: | ||
| 135 | case SwkbdType.Korean: | ||
| 136 | default: | ||
| 137 | input_type = InputType.TYPE_CLASS_TEXT; | ||
| 138 | if (config.password_mode == SwkbdPasswordMode.Enabled) | ||
| 139 | { | ||
| 140 | input_type |= InputType.TYPE_TEXT_VARIATION_PASSWORD; | ||
| 141 | } | ||
| 142 | break; | ||
| 143 | case SwkbdType.NumberPad: | ||
| 144 | input_type = InputType.TYPE_CLASS_NUMBER; | ||
| 145 | if (config.password_mode == SwkbdPasswordMode.Enabled) | ||
| 146 | { | ||
| 147 | input_type |= InputType.TYPE_NUMBER_VARIATION_PASSWORD; | ||
| 148 | } | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | |||
| 152 | // Apply input type | ||
| 153 | editText.setInputType(input_type); | ||
| 154 | |||
| 155 | FrameLayout container = new FrameLayout(emulationActivity); | ||
| 156 | container.addView(editText); | ||
| 157 | |||
| 158 | String headerText = config.header_text.isEmpty() ? emulationActivity.getString(R.string.software_keyboard) : config.header_text; | ||
| 159 | String okText = config.header_text.isEmpty() ? emulationActivity.getString(android.R.string.ok) : config.ok_text; | ||
| 160 | |||
| 161 | MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity) | ||
| 162 | .setTitle(headerText) | ||
| 163 | .setView(container); | ||
| 164 | setCancelable(false); | ||
| 165 | |||
| 166 | builder.setPositiveButton(okText, null); | ||
| 167 | builder.setNegativeButton(emulationActivity.getString(android.R.string.cancel), null); | ||
| 168 | |||
| 169 | final AlertDialog dialog = builder.create(); | ||
| 170 | dialog.create(); | ||
| 171 | if (dialog.getButton(DialogInterface.BUTTON_POSITIVE) != null) { | ||
| 172 | dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener((view) -> { | ||
| 173 | data.result = SwkbdResult.Ok; | ||
| 174 | data.text = editText.getText().toString(); | ||
| 175 | dialog.dismiss(); | ||
| 176 | |||
| 177 | synchronized (finishLock) { | ||
| 178 | finishLock.notifyAll(); | ||
| 179 | } | ||
| 180 | }); | ||
| 181 | } | ||
| 182 | if (dialog.getButton(DialogInterface.BUTTON_NEUTRAL) != null) { | ||
| 183 | dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener((view) -> { | ||
| 184 | data.result = SwkbdResult.Ok; | ||
| 185 | dialog.dismiss(); | ||
| 186 | synchronized (finishLock) { | ||
| 187 | finishLock.notifyAll(); | ||
| 188 | } | ||
| 189 | }); | ||
| 190 | } | ||
| 191 | if (dialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) { | ||
| 192 | dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((view) -> { | ||
| 193 | data.result = SwkbdResult.Cancel; | ||
| 194 | dialog.dismiss(); | ||
| 195 | synchronized (finishLock) { | ||
| 196 | finishLock.notifyAll(); | ||
| 197 | } | ||
| 198 | }); | ||
| 199 | } | ||
| 200 | |||
| 201 | return dialog; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | private static KeyboardData data; | ||
| 206 | private static final Object finishLock = new Object(); | ||
| 207 | |||
| 208 | private static void ExecuteNormalImpl(KeyboardConfig config) { | ||
| 209 | final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get(); | ||
| 210 | |||
| 211 | data = new KeyboardData(SwkbdResult.Cancel, ""); | ||
| 212 | |||
| 213 | KeyboardDialogFragment fragment = KeyboardDialogFragment.newInstance(config); | ||
| 214 | fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard"); | ||
| 215 | } | ||
| 216 | |||
| 217 | private static void ExecuteInlineImpl(KeyboardConfig config) { | ||
| 218 | final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get(); | ||
| 219 | |||
| 220 | var overlayView = emulationActivity.findViewById(R.id.surface_input_overlay); | ||
| 221 | InputMethodManager im = (InputMethodManager)overlayView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); | ||
| 222 | im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED); | ||
| 223 | |||
| 224 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. | ||
| 225 | final Handler handler = new Handler(); | ||
| 226 | final int delayMs = 500; | ||
| 227 | handler.postDelayed(new Runnable() { | ||
| 228 | public void run() { | ||
| 229 | var insets = ViewCompat.getRootWindowInsets(overlayView); | ||
| 230 | var isKeyboardVisible = insets.isVisible(WindowInsets.Type.ime()); | ||
| 231 | if (isKeyboardVisible) { | ||
| 232 | handler.postDelayed(this, delayMs); | ||
| 233 | return; | ||
| 234 | } | ||
| 235 | |||
| 236 | // No longer visible, submit the result. | ||
| 237 | NativeLibrary.SubmitInlineKeyboardInput(android.view.KeyEvent.KEYCODE_ENTER); | ||
| 238 | } | ||
| 239 | }, delayMs); | ||
| 240 | } | ||
| 241 | |||
| 242 | public static KeyboardData ExecuteNormal(KeyboardConfig config) { | ||
| 243 | NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteNormalImpl(config)); | ||
| 244 | |||
| 245 | synchronized (finishLock) { | ||
| 246 | try { | ||
| 247 | finishLock.wait(); | ||
| 248 | } catch (Exception ignored) { | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | return data; | ||
| 253 | } | ||
| 254 | |||
| 255 | public static void ExecuteInline(KeyboardConfig config) { | ||
| 256 | NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteInlineImpl(config)); | ||
| 257 | } | ||
| 258 | |||
| 259 | public static void ShowError(String error) { | ||
| 260 | NativeLibrary.displayAlertMsg( | ||
| 261 | YuzuApplication.getAppContext().getResources().getString(R.string.software_keyboard), | ||
| 262 | error, false); | ||
| 263 | } | ||
| 264 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt new file mode 100644 index 000000000..704109ec0 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.applets.keyboard | ||
| 5 | |||
| 6 | import android.content.Context | ||
| 7 | import android.os.Handler | ||
| 8 | import android.os.Looper | ||
| 9 | import android.view.KeyEvent | ||
| 10 | import android.view.View | ||
| 11 | import android.view.WindowInsets | ||
| 12 | import android.view.inputmethod.InputMethodManager | ||
| 13 | import androidx.core.view.ViewCompat | ||
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 15 | import org.yuzu.yuzu_emu.R | ||
| 16 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment | ||
| 17 | import java.io.Serializable | ||
| 18 | |||
| 19 | object SoftwareKeyboard { | ||
| 20 | lateinit var data: KeyboardData | ||
| 21 | val dataLock = Object() | ||
| 22 | |||
| 23 | private fun executeNormalImpl(config: KeyboardConfig) { | ||
| 24 | val emulationActivity = NativeLibrary.sEmulationActivity.get() | ||
| 25 | data = KeyboardData(SwkbdResult.Cancel.ordinal, "") | ||
| 26 | val fragment = KeyboardDialogFragment.newInstance(config) | ||
| 27 | fragment.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG) | ||
| 28 | } | ||
| 29 | |||
| 30 | private fun executeInlineImpl(config: KeyboardConfig) { | ||
| 31 | val emulationActivity = NativeLibrary.sEmulationActivity.get() | ||
| 32 | |||
| 33 | val overlayView = emulationActivity!!.findViewById<View>(R.id.surface_input_overlay) | ||
| 34 | val im = | ||
| 35 | overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | ||
| 36 | im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED) | ||
| 37 | |||
| 38 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. | ||
| 39 | val handler = Handler(Looper.myLooper()!!) | ||
| 40 | val delayMs = 500 | ||
| 41 | handler.postDelayed(object : Runnable { | ||
| 42 | override fun run() { | ||
| 43 | val insets = ViewCompat.getRootWindowInsets(overlayView) | ||
| 44 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) | ||
| 45 | if (isKeyboardVisible) { | ||
| 46 | handler.postDelayed(this, delayMs.toLong()) | ||
| 47 | return | ||
| 48 | } | ||
| 49 | |||
| 50 | // No longer visible, submit the result. | ||
| 51 | NativeLibrary.SubmitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) | ||
| 52 | } | ||
| 53 | }, delayMs.toLong()) | ||
| 54 | } | ||
| 55 | |||
| 56 | @JvmStatic | ||
| 57 | fun executeNormal(config: KeyboardConfig): KeyboardData { | ||
| 58 | NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeNormalImpl(config) } | ||
| 59 | synchronized(dataLock) { | ||
| 60 | dataLock.wait() | ||
| 61 | } | ||
| 62 | return data | ||
| 63 | } | ||
| 64 | |||
| 65 | @JvmStatic | ||
| 66 | fun executeInline(config: KeyboardConfig) { | ||
| 67 | NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeInlineImpl(config) } | ||
| 68 | } | ||
| 69 | |||
| 70 | // Corresponds to Service::AM::Applets::SwkbdType | ||
| 71 | enum class SwkbdType { | ||
| 72 | Normal, | ||
| 73 | NumberPad, | ||
| 74 | Qwerty, | ||
| 75 | Unknown3, | ||
| 76 | Latin, | ||
| 77 | SimplifiedChinese, | ||
| 78 | TraditionalChinese, | ||
| 79 | Korean | ||
| 80 | } | ||
| 81 | |||
| 82 | // Corresponds to Service::AM::Applets::SwkbdPasswordMode | ||
| 83 | enum class SwkbdPasswordMode { | ||
| 84 | Disabled, | ||
| 85 | Enabled | ||
| 86 | } | ||
| 87 | |||
| 88 | // Corresponds to Service::AM::Applets::SwkbdResult | ||
| 89 | enum class SwkbdResult { | ||
| 90 | Ok, | ||
| 91 | Cancel | ||
| 92 | } | ||
| 93 | |||
| 94 | data class KeyboardConfig( | ||
| 95 | var ok_text: String? = null, | ||
| 96 | var header_text: String? = null, | ||
| 97 | var sub_text: String? = null, | ||
| 98 | var guide_text: String? = null, | ||
| 99 | var initial_text: String? = null, | ||
| 100 | var left_optional_symbol_key: Short = 0, | ||
| 101 | var right_optional_symbol_key: Short = 0, | ||
| 102 | var max_text_length: Int = 0, | ||
| 103 | var min_text_length: Int = 0, | ||
| 104 | var initial_cursor_position: Int = 0, | ||
| 105 | var type: Int = 0, | ||
| 106 | var password_mode: Int = 0, | ||
| 107 | var text_draw_type: Int = 0, | ||
| 108 | var key_disable_flags: Int = 0, | ||
| 109 | var use_blur_background: Boolean = false, | ||
| 110 | var enable_backspace_button: Boolean = false, | ||
| 111 | var enable_return_button: Boolean = false, | ||
| 112 | var disable_cancel_button: Boolean = false | ||
| 113 | ) : Serializable | ||
| 114 | |||
| 115 | // Corresponds to Frontend::KeyboardData | ||
| 116 | data class KeyboardData(var result: Int, var text: String) | ||
| 117 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt new file mode 100644 index 000000000..2428bd261 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.applets.keyboard.ui | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.DialogInterface | ||
| 8 | import android.os.Bundle | ||
| 9 | import android.text.InputFilter | ||
| 10 | import android.text.InputType | ||
| 11 | import androidx.fragment.app.DialogFragment | ||
| 12 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 13 | import org.yuzu.yuzu_emu.R | ||
| 14 | import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard | ||
| 15 | import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard.KeyboardConfig | ||
| 16 | import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding | ||
| 17 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | ||
| 18 | |||
| 19 | class KeyboardDialogFragment : DialogFragment() { | ||
| 20 | private lateinit var binding: DialogEditTextBinding | ||
| 21 | private lateinit var config: KeyboardConfig | ||
| 22 | |||
| 23 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| 24 | binding = DialogEditTextBinding.inflate(layoutInflater) | ||
| 25 | config = requireArguments().serializable(CONFIG)!! | ||
| 26 | |||
| 27 | // Set up the input | ||
| 28 | binding.editText.hint = config.initial_text | ||
| 29 | binding.editText.isSingleLine = !config.enable_return_button | ||
| 30 | binding.editText.filters = | ||
| 31 | arrayOf<InputFilter>(InputFilter.LengthFilter(config.max_text_length)) | ||
| 32 | |||
| 33 | // Handle input type | ||
| 34 | var inputType: Int | ||
| 35 | when (config.type) { | ||
| 36 | SoftwareKeyboard.SwkbdType.Normal.ordinal, | ||
| 37 | SoftwareKeyboard.SwkbdType.Qwerty.ordinal, | ||
| 38 | SoftwareKeyboard.SwkbdType.Unknown3.ordinal, | ||
| 39 | SoftwareKeyboard.SwkbdType.Latin.ordinal, | ||
| 40 | SoftwareKeyboard.SwkbdType.SimplifiedChinese.ordinal, | ||
| 41 | SoftwareKeyboard.SwkbdType.TraditionalChinese.ordinal, | ||
| 42 | SoftwareKeyboard.SwkbdType.Korean.ordinal -> { | ||
| 43 | inputType = InputType.TYPE_CLASS_TEXT | ||
| 44 | if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) { | ||
| 45 | inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD | ||
| 46 | } | ||
| 47 | } | ||
| 48 | SoftwareKeyboard.SwkbdType.NumberPad.ordinal -> { | ||
| 49 | inputType = InputType.TYPE_CLASS_NUMBER | ||
| 50 | if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) { | ||
| 51 | inputType = inputType or InputType.TYPE_NUMBER_VARIATION_PASSWORD | ||
| 52 | } | ||
| 53 | } | ||
| 54 | else -> { | ||
| 55 | inputType = InputType.TYPE_CLASS_TEXT | ||
| 56 | if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) { | ||
| 57 | inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | binding.editText.inputType = inputType | ||
| 62 | |||
| 63 | val headerText = | ||
| 64 | config.header_text!!.ifEmpty { resources.getString(R.string.software_keyboard) } | ||
| 65 | val okText = | ||
| 66 | if (config.header_text!!.isEmpty()) resources.getString(android.R.string.ok) else config.ok_text!! | ||
| 67 | |||
| 68 | return MaterialAlertDialogBuilder(requireContext()) | ||
| 69 | .setTitle(headerText) | ||
| 70 | .setView(binding.root) | ||
| 71 | .setPositiveButton(okText) { _, _ -> | ||
| 72 | SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Ok.ordinal | ||
| 73 | SoftwareKeyboard.data.text = binding.editText.text.toString() | ||
| 74 | } | ||
| 75 | .setNegativeButton(resources.getString(android.R.string.cancel)) { _, _ -> | ||
| 76 | SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Cancel.ordinal | ||
| 77 | } | ||
| 78 | .create() | ||
| 79 | } | ||
| 80 | |||
| 81 | override fun onDismiss(dialog: DialogInterface) { | ||
| 82 | super.onDismiss(dialog) | ||
| 83 | synchronized(SoftwareKeyboard.dataLock) { | ||
| 84 | SoftwareKeyboard.dataLock.notifyAll() | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | companion object { | ||
| 89 | const val TAG = "KeyboardDialogFragment" | ||
| 90 | const val CONFIG = "keyboard_config" | ||
| 91 | |||
| 92 | fun newInstance(config: KeyboardConfig?): KeyboardDialogFragment { | ||
| 93 | val frag = KeyboardDialogFragment() | ||
| 94 | val args = Bundle() | ||
| 95 | args.putSerializable(CONFIG, config) | ||
| 96 | frag.arguments = args | ||
| 97 | return frag | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
diff --git a/src/android/app/src/main/jni/applets/software_keyboard.cpp b/src/android/app/src/main/jni/applets/software_keyboard.cpp index 278137b4c..c6fffbbab 100644 --- a/src/android/app/src/main/jni/applets/software_keyboard.cpp +++ b/src/android/app/src/main/jni/applets/software_keyboard.cpp | |||
| @@ -253,19 +253,19 @@ void AndroidKeyboard::SubmitNormalText(const ResultData& data) const { | |||
| 253 | 253 | ||
| 254 | void InitJNI(JNIEnv* env) { | 254 | void InitJNI(JNIEnv* env) { |
| 255 | s_software_keyboard_class = reinterpret_cast<jclass>( | 255 | s_software_keyboard_class = reinterpret_cast<jclass>( |
| 256 | env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard"))); | 256 | env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard"))); |
| 257 | s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( | 257 | s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
| 258 | env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig"))); | 258 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig"))); |
| 259 | s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( | 259 | s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
| 260 | env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardData"))); | 260 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData"))); |
| 261 | 261 | ||
| 262 | s_swkbd_execute_normal = env->GetStaticMethodID( | 262 | s_swkbd_execute_normal = env->GetStaticMethodID( |
| 263 | s_software_keyboard_class, "ExecuteNormal", | 263 | s_software_keyboard_class, "executeNormal", |
| 264 | "(Lorg/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" | 264 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" |
| 265 | "applets/SoftwareKeyboard$KeyboardData;"); | 265 | "applets/keyboard/SoftwareKeyboard$KeyboardData;"); |
| 266 | s_swkbd_execute_inline = | 266 | s_swkbd_execute_inline = |
| 267 | env->GetStaticMethodID(s_software_keyboard_class, "ExecuteInline", | 267 | env->GetStaticMethodID(s_software_keyboard_class, "executeInline", |
| 268 | "(Lorg/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig;)V"); | 268 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V"); |
| 269 | } | 269 | } |
| 270 | 270 | ||
| 271 | void CleanupJNI(JNIEnv* env) { | 271 | void CleanupJNI(JNIEnv* env) { |
diff --git a/src/android/app/src/main/res/layout/dialog_edit_text.xml b/src/android/app/src/main/res/layout/dialog_edit_text.xml new file mode 100644 index 000000000..58b905d71 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_edit_text.xml | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <androidx.constraintlayout.widget.ConstraintLayout | ||
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 5 | android:layout_width="match_parent" | ||
| 6 | android:layout_height="match_parent"> | ||
| 7 | |||
| 8 | <com.google.android.material.textfield.TextInputLayout | ||
| 9 | android:id="@+id/edit_text_layout" | ||
| 10 | android:layout_width="match_parent" | ||
| 11 | android:layout_height="wrap_content" | ||
| 12 | android:layout_margin="24dp" | ||
| 13 | app:layout_constraintTop_toTopOf="parent"> | ||
| 14 | |||
| 15 | <com.google.android.material.textfield.TextInputEditText | ||
| 16 | android:id="@+id/edit_text" | ||
| 17 | android:layout_width="match_parent" | ||
| 18 | android:layout_height="wrap_content" | ||
| 19 | android:inputType="none" /> | ||
| 20 | |||
| 21 | </com.google.android.material.textfield.TextInputLayout> | ||
| 22 | |||
| 23 | </androidx.constraintlayout.widget.ConstraintLayout> | ||