android - add basic software keyboard support

This commit is contained in:
Emmanuel Hansen 2023-12-28 09:43:03 +00:00
parent c12da6d650
commit b81643352e
12 changed files with 687 additions and 20 deletions

View File

@ -1,4 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging.Formatters;
using Ryujinx.Common.Logging.Targets;
using System;
@ -12,7 +12,7 @@ namespace LibRyujinx
string ILogTarget.Name { get => _name; }
public AndroidLogTarget( string name)
public AndroidLogTarget(string name)
{
_name = name;
_formatter = new DefaultLogFormatter();

View File

@ -0,0 +1,136 @@
using LibHac.Tools.Fs;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.Ui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LibRyujinx.Android
{
internal class AndroidUiHandler : IHostUiHandler, IDisposable
{
public IHostUiTheme HostUiTheme => throw new NotImplementedException();
public ManualResetEvent _waitEvent;
public ManualResetEvent _responseEvent;
private bool _isDisposed;
private bool _isOkPressed;
private long _input;
public AndroidUiHandler()
{
_waitEvent = new ManualResetEvent(false);
_responseEvent = new ManualResetEvent(false);
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
{
throw new NotImplementedException();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
{
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? ""));
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? ""));
LibRyujinx.setUiHandlerType(1);
_responseEvent.Reset();
Set();
_responseEvent.WaitOne();
return _isOkPressed;
}
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
{
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString("Software Keyboard"));
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(args.HeaderText ?? ""));
LibRyujinx.setUiHandlerWatermark(LibRyujinx.storeString(args.GuideText ?? ""));
LibRyujinx.setUiHandlerSubtitle(LibRyujinx.storeString(args.SubtitleText ?? ""));
LibRyujinx.setUiHandlerInitialText(LibRyujinx.storeString(args.InitialText ?? ""));
LibRyujinx.setUiHandlerMinLength(args.StringLengthMin);
LibRyujinx.setUiHandlerMaxLength(args.StringLengthMax);
LibRyujinx.setUiHandlerType(2);
LibRyujinx.setUiHandlerKeyboardMode((int)args.KeyboardMode);
_responseEvent.Reset();
Set();
_responseEvent.WaitOne();
userText = _input != -1 ? LibRyujinx.GetStoredString(_input) : "";
return _isOkPressed;
}
public bool DisplayMessageDialog(string title, string message)
{
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? ""));
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? ""));
LibRyujinx.setUiHandlerType(1);
_responseEvent.Reset();
Set();
_responseEvent.WaitOne();
return _isOkPressed;
}
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
{
string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
string message = $"Application requests **{playerCount}** player(s) with:\n\n"
+ $"**TYPES:** {args.SupportedStyles}\n\n"
+ $"**PLAYERS:** {string.Join(", ", args.SupportedPlayers)}\n\n"
+ (args.IsDocked ? "Docked mode set. `Handheld` is also invalid.\n\n" : "")
+ "_Please reconfigure Input now and then press OK._";
return DisplayMessageDialog("Controller Applet", message);
}
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
{
// throw new NotImplementedException();
}
internal void Wait()
{
if (_isDisposed)
return;
_waitEvent.Reset();
_waitEvent.WaitOne();
_waitEvent.Reset();
}
internal void Set()
{
if (_isDisposed)
return;
_waitEvent.Set();
}
internal void SetResponse(bool isOkPressed, long input)
{
if (_isDisposed)
return;
_isOkPressed = isOkPressed;
_input = input;
_responseEvent.Set();
}
public void Dispose()
{
_isDisposed = true;
_waitEvent.Set();
_waitEvent.Set();
_responseEvent.Dispose();
_waitEvent.Dispose();
}
}
}

View File

@ -42,11 +42,36 @@ namespace LibRyujinx
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
[DllImport("libryujinxjni")]
private extern static long storeString(string ch);
[DllImport("libryujinxjni")]
private extern static IntPtr getString(long id);
internal extern static long storeString(string ch);
private static string GetStoredString(long id)
[DllImport("libryujinxjni")]
internal extern static IntPtr getString(long id);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerTitle(long title);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMessage(long message);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerWatermark(long watermark);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerInitialText(long text);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerSubtitle(long text);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerType(int type);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerKeyboardMode(int mode);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMinLength(int lenght);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMaxLength(int lenght);
internal static string GetStoredString(long id)
{
var pointer = getString(id);
if (pointer != IntPtr.Zero)
@ -91,7 +116,7 @@ namespace LibRyujinx
Logger.AddTarget(
new AsyncLogTargetWrapper(
new AndroidLogTarget("Ryujinx"),
new AndroidLogTarget("RyujinxLog"),
1000,
AsyncLogTargetOverflowAction.Block
));
@ -709,6 +734,30 @@ namespace LibRyujinx
DeleteUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")]
public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
{
SetupUiHandler();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")]
public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
{
WaitUiHandler();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")]
public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj)
{
StopUiHandlerWait();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")]
public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input)
{
SetUiHandlerResponse(isOkPressed, input);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
{

View File

@ -37,6 +37,8 @@ using System.Collections.Generic;
using LibHac.Bcat;
using Ryujinx.Ui.App.Common;
using System.Text;
using Ryujinx.HLE.Ui;
using LibRyujinx.Android;
namespace LibRyujinx
{
@ -142,7 +144,9 @@ namespace LibRyujinx
var gameInfo = new GameInfo
{
FileSize = gameStream.Length * 0.000000000931, TitleName = "Unknown", TitleId = "0000000000000000",
FileSize = gameStream.Length * 0.000000000931,
TitleName = "Unknown",
TitleId = "0000000000000000",
Developer = "Unknown",
Version = "0",
Icon = null
@ -629,7 +633,7 @@ namespace LibRyujinx
public static List<string> GetDlcContentList(string path, ulong titleId)
{
if(!File.Exists(path))
if (!File.Exists(path))
return new List<string>();
using FileStream containerFile = File.OpenRead(path);
@ -665,6 +669,38 @@ namespace LibRyujinx
return paths;
}
public static void SetupUiHandler()
{
if (SwitchDevice is { } switchDevice)
{
switchDevice.HostUiHandler = new AndroidUiHandler();
}
}
public static void WaitUiHandler()
{
if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler)
{
uiHandler.Wait();
}
}
public static void StopUiHandlerWait()
{
if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler)
{
uiHandler.Set();
}
}
public static void SetUiHandlerResponse(bool isOkPressed, long input)
{
if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler)
{
uiHandler.SetResponse(isOkPressed, input);
}
}
}
public class SwitchDevice : IDisposable
@ -677,6 +713,7 @@ namespace LibRyujinx
public UserChannelPersistence UserChannelPersistence { get; set; }
public InputManager? InputManager { get; set; }
public Switch? EmulationContext { get; set; }
public IHostUiHandler? HostUiHandler { get; set; }
public void Dispose()
{
@ -741,7 +778,7 @@ namespace LibRyujinx
renderer,
LibRyujinx.AudioDriver, //Audio
MemoryConfiguration.MemoryConfiguration4GiB,
null,
HostUiHandler,
systemLanguage,
regionCode,
enableVsync,

View File

@ -38,6 +38,36 @@
void* _ryujinxNative = NULL;
class UiHandler {
public:
void setTitle(long storedTitle);
void setMessage(long storedMessage);
void setWatermark(long wm);
void setType(int t);
void setMode(int t);
void setMinLength(int t);
void setMaxLength(int t);
void setInitialText(long text);
void setSubtitle(long text);
long getTitle();
long getMessage();
long getWatermark();
long getInitialText();
long getSubtitle();
int type = 0;
int keyboardMode = 0;
int min_length = -1;
int max_length = -1;
private:
long title = -1;
long message = -1;
long watermark = -1;
long initialText = -1;
long subtitle = -1;
};
// Ryujinx imported functions
bool (*initialize)(char*) = NULL;
@ -46,7 +76,7 @@ long _currentRenderingThreadId = 0;
JavaVM* _vm = nullptr;
jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr;
std::string _currentString = "";
string_helper str_helper = string_helper();
UiHandler ui_handler = UiHandler();
#endif //RYUJINXNATIVE_RYUIJNX_H

View File

@ -327,6 +327,37 @@ const char* getString(long id){
return cstr;
}
extern "C"
{
void setUiHandlerTitle(long title) {
ui_handler.setTitle(title);
}
void setUiHandlerMessage(long message) {
ui_handler.setMessage(message);
}
void setUiHandlerWatermark(long wm) {
ui_handler.setWatermark(wm);
}
void setUiHandlerType(int type) {
ui_handler.setType(type);
}
void setUiHandlerKeyboardMode(int mode) {
ui_handler.setMode(mode);
}
void setUiHandlerMinLength(int length) {
ui_handler.setMinLength(length);
}
void setUiHandlerMaxLength(int length) {
ui_handler.setMaxLength(length);
}
void setUiHandlerInitialText(long text) {
ui_handler.setInitialText(text);
}
void setUiHandlerSubtitle(long text) {
ui_handler.setSubtitle(text);
}
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz, jstring string) {
@ -337,7 +368,7 @@ Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz
extern "C"
JNIEXPORT jstring JNICALL
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong id) {
return createStringFromStdString(env, str_helper.get_stored(id));
return createStringFromStdString(env, id > -1 ? str_helper.get_stored(id) : "");
}
extern "C"
@ -346,3 +377,145 @@ Java_org_ryujinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *en
jboolean is_flipped) {
isInitialOrientationFlipped = is_flipped;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestType(JNIEnv *env, jobject thiz) {
return ui_handler.type;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestTitle(JNIEnv *env, jobject thiz) {
return ui_handler.getTitle();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestMessage(JNIEnv *env, jobject thiz) {
return ui_handler.getMessage();
}
void UiHandler::setTitle(long storedTitle) {
if(title != -1){
str_helper.get_stored(title);
title = -1;
}
title = storedTitle;
}
void UiHandler::setMessage(long storedMessage) {
if(message != -1){
str_helper.get_stored(message);
message = -1;
}
message = storedMessage;
}
void UiHandler::setType(int t) {
this->type = t;
}
long UiHandler::getTitle() {
auto v = title;
title = -1;
return v;
}
long UiHandler::getMessage() {
auto v = message;
message = -1;
return v;
}
void UiHandler::setWatermark(long wm) {
if(watermark != -1){
str_helper.get_stored(watermark);
watermark = -1;
}
watermark = wm;
}
void UiHandler::setMinLength(int t) {
this->min_length = t;
}
void UiHandler::setMaxLength(int t) {
this->max_length = t;
}
long UiHandler::getWatermark() {
auto v = watermark;
watermark = -1;
return v;
}
void UiHandler::setInitialText(long text) {
if(initialText != -1){
str_helper.get_stored(watermark);
initialText = -1;
}
initialText = text;
}
void UiHandler::setSubtitle(long text) {
if(subtitle != -1){
str_helper.get_stored(subtitle);
subtitle = -1;
}
subtitle = text;
}
long UiHandler::getInitialText() {
auto v = initialText;
initialText = -1;
return v;
}
long UiHandler::getSubtitle() {
auto v = subtitle;
subtitle = -1;
return v;
}
void UiHandler::setMode(int t) {
keyboardMode = t;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMinLength(JNIEnv *env, jobject thiz) {
return ui_handler.min_length;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMaxLength(JNIEnv *env, jobject thiz) {
return ui_handler.max_length;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestWatermark(JNIEnv *env, jobject thiz) {
return ui_handler.getWatermark();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestInitialText(JNIEnv *env, jobject thiz) {
return ui_handler.getInitialText();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestSubtitle(JNIEnv *env, jobject thiz) {
return ui_handler.getSubtitle();
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerKeyboardMode(JNIEnv *env, jobject thiz) {
return ui_handler.keyboardMode;
}

View File

@ -75,6 +75,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
_isInit = false
_isStarted = false
mainViewModel.activity.uiHandler.stop()
_updateThread?.join()
_renderingThreadWatcher?.join()
}
@ -161,6 +163,10 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
mainViewModel.performanceManager?.closeCurrentRenderingSession()
}
}
thread {
mainViewModel.activity.uiHandler.listen()
}
_nativeRyujinx.graphicsRendererRunLoop()
game?.close()

View File

@ -18,6 +18,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.anggrayudi.storage.SimpleStorageHelper
import com.halilibo.richtext.ui.RichTextThemeIntegration
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings
@ -32,6 +33,7 @@ class MainActivity : BaseActivity() {
private var _isInit: Boolean = false
var isGameRunning = false
var storageHelper: SimpleStorageHelper? = null
lateinit var uiHandler: UiHandler
companion object {
var mainViewModel: MainViewModel? = null
var AppPath : String = ""
@ -77,6 +79,8 @@ class MainActivity : BaseActivity() {
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, quickSettings.enableGuestLogs)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, quickSettings.enableTraceLogs)
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
uiHandler = UiHandler()
_isInit = success
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -107,12 +111,14 @@ class MainActivity : BaseActivity() {
mainViewModel?.apply {
setContent {
RyujinxAndroidTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainView.Main(mainViewModel = this)
RichTextThemeIntegration(contentColor = { MaterialTheme.colorScheme.onSurface }) {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainView.Main(mainViewModel = this)
}
}
}
}

View File

@ -32,4 +32,13 @@ class NativeHelpers {
external fun storeStringJava(string: String) : Long
external fun getStringJava(id: Long) : String
external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
external fun getUiHandlerRequestType() : Int
external fun getUiHandlerRequestTitle() : Long
external fun getUiHandlerRequestMessage() : Long
external fun getUiHandlerMinLength() : Int
external fun getUiHandlerMaxLength() : Int
external fun getUiHandlerKeyboardMode() : Int
external fun getUiHandlerRequestWatermark() : Long
external fun getUiHandlerRequestInitialText() : Long
external fun getUiHandlerRequestSubtitle() : Long
}

View File

@ -74,4 +74,8 @@ class RyujinxNative {
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
external fun deviceGetInstalledFirmwareVersion() : Long
external fun uiHandlerSetup()
external fun uiHandlerWait()
external fun uiHandlerStopWait()
external fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
}

View File

@ -0,0 +1,215 @@
package org.ryujinx.android
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichText
internal enum class KeyboardMode {
Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin
}
class UiHandler {
private var initialText: String = ""
private var subtitle: String = ""
private var maxLength: Int = 0
private var minLength: Int = 0
private var watermark: String = ""
private var type: Int = -1
private var mode: KeyboardMode = KeyboardMode.Default
val showMessage = mutableStateOf(false)
val inputText = mutableStateOf("")
var title: String = ""
var message: String = ""
var shouldListen = true
init {
RyujinxNative.instance.uiHandlerSetup()
}
fun listen() {
showMessage.value = false
while (shouldListen) {
RyujinxNative.instance.uiHandlerWait()
title =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
message =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestMessage())
watermark =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestWatermark())
type = NativeHelpers.instance.getUiHandlerRequestType()
minLength = NativeHelpers.instance.getUiHandlerMinLength()
maxLength = NativeHelpers.instance.getUiHandlerMaxLength()
mode = KeyboardMode.values()[NativeHelpers.instance.getUiHandlerKeyboardMode()]
subtitle =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestSubtitle())
initialText =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestInitialText())
inputText.value = initialText
showMessage.value = type > 0
}
}
fun stop() {
shouldListen = false
RyujinxNative.instance.uiHandlerStopWait()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Compose() {
val showMessageListener = remember {
showMessage
}
val inputListener = remember {
inputText
}
val validation = remember {
mutableStateOf("")
}
fun validate() : Boolean{
if(inputText.value.isEmpty()){
validation.value = "Must be between ${minLength} and ${maxLength} characters"
}
else{
return inputText.value.length < minLength || inputText.value.length > maxLength
}
return false;
}
fun getInputType(): KeyboardType {
return when(mode){
KeyboardMode.Default -> KeyboardType.Text
KeyboardMode.Numeric -> KeyboardType.Decimal
KeyboardMode.ASCII -> KeyboardType.Ascii
else -> { KeyboardType.Text}
}
}
fun submit() {
var input: Long = -1
if (type == 2) {
if (inputListener.value.length < minLength || inputListener.value.length > maxLength)
return
input =
NativeHelpers.instance.storeStringJava(inputListener.value)
}
showMessageListener.value = false
RyujinxNative.instance.uiHandlerSetResponse(true, input)
}
if (showMessageListener.value) {
AlertDialog(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = false, false)
) {
Column {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(text = title)
Column(
modifier = Modifier
.height(128.dp)
.verticalScroll(rememberScrollState())
.padding(8.dp),
verticalArrangement = Arrangement.Center
) {
RichText {
Markdown(content = message)
}
if (type == 2) {
validate()
if (watermark.isNotEmpty())
TextField(
value = inputListener.value,
onValueChange = { inputListener.value = it },
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
label = {
Text(text = watermark)
},
keyboardOptions = KeyboardOptions(keyboardType = getInputType()),
isError = validate()
)
else
TextField(
value = inputListener.value,
onValueChange = { inputListener.value = it },
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
keyboardOptions = KeyboardOptions(
keyboardType = getInputType(),
imeAction = ImeAction.Done
),
isError = validate(),
singleLine = true,
keyboardActions = KeyboardActions(onDone = { submit() })
)
if (subtitle.isNotEmpty())
Text(text = subtitle)
Text(text = validation.value)
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
) {
Button(onClick = {
submit()
}) {
Text(text = "OK")
}
}
}
}
}
}
}
}
}

View File

@ -285,6 +285,8 @@ class GameViews {
}
}
}
mainViewModel.activity.uiHandler.Compose()
}
}