diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e62566..b66ef9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,8 @@ endif() if (EMSCRIPTEN) set(SRC_MENU menu_emscripten.c) +elseif (ANDROID) + set(SRC_MENU menu_android.c) elseif (GTK2_FOUND) set(SRC_MENU menu.c menu_callback.c menu_gui.c s39init.c) diff --git a/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt b/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt index 0057ef1..aaa53aa 100644 --- a/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt +++ b/android/app/src/main/java/io/github/kichikuou/xsystem35/GameActivity.kt @@ -17,9 +17,13 @@ */ package io.github.kichikuou.xsystem35 +import android.app.AlertDialog import android.media.MediaPlayer import android.os.Bundle +import android.text.InputType import android.util.Log +import android.widget.EditText +import android.widget.NumberPicker import org.libsdl.app.SDLActivity import java.io.File import java.io.IOException @@ -74,13 +78,81 @@ class GameActivity : SDLActivity() { Launcher.updateGameList() } - // These functions are called in the SDL thread by JNI. + private fun textInputDialog(msg: String, oldVal: String, result: Array) { + val input = EditText(this) + input.inputType = InputType.TYPE_CLASS_TEXT + input.setText(oldVal) + AlertDialog.Builder(this) + .setMessage(msg) + .setView(input) + .setPositiveButton(R.string.ok) {_, _ -> + result[0] = input.text.toString() + } + .setNegativeButton(R.string.cancel) {_, _ -> } + .setOnDismissListener { + synchronized(result) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (result as Object).notify() + } + } + .show() + } + + private fun numberInputDialog(msg: String, min: Int, max: Int, initial: Int, result: IntArray) { + val input = NumberPicker(this) + input.minValue = min + input.maxValue = max + input.value = initial + AlertDialog.Builder(this) + .setMessage(msg) + .setView(input) + .setPositiveButton(R.string.ok) {_, _ -> + input.clearFocus() + result[0] = input.value + } + .setNegativeButton(R.string.cancel) {_, _ -> } + .setOnDismissListener { + synchronized(result) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (result as Object).notify() + } + } + .show() + } + + // The functions below are called in the SDL thread by JNI. @Suppress("unused") fun cddaStart(track: Int, loop: Boolean) = cdda.start(track, loop) @Suppress("unused") fun cddaStop() = cdda.stop() @Suppress("unused") fun cddaCurrentPosition() = cdda.currentPosition() @Suppress("unused") fun midiStart(path: String, loop: Boolean) = midi.start(path, loop) @Suppress("unused") fun midiStop() = midi.stop() @Suppress("unused") fun midiCurrentPosition() = midi.currentPosition() + + @Suppress("unused") fun inputString(msg: String, oldVal: String): String? { + val result = arrayOfNulls(1) + runOnUiThread { textInputDialog(msg, oldVal, result) } + // Block the calling thread. + synchronized(result) { + try { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (result as Object).wait() + } catch (ex: InterruptedException) { + ex.printStackTrace() + } + } + return result[0] + } + + @Suppress("unused") fun inputNumber(msg: String, min: Int, max: Int, initial: Int): Int { + val result = intArrayOf(-1) + runOnUiThread { numberInputDialog(msg, min, max, initial, result) } + // Block the calling thread. + synchronized(result) { + try { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (result as Object).wait() + } catch (ex: InterruptedException) { + ex.printStackTrace() + } + } + return result[0] + } } private class CddaPlayer(private val playlistPath: File) { diff --git a/src/cmdn.c b/src/cmdn.c index 59d35b7..0369a03 100644 --- a/src/cmdn.c +++ b/src/cmdn.c @@ -23,6 +23,7 @@ #include #include +#include #include #include "portab.h" #include "utfsjis.h" @@ -359,6 +360,8 @@ void commandNI() { /* From Panyo */ ni_param.def = def; ni_param.max = _max; ni_param.min = _min; + if (!ni_param.title) + ni_param.title = strdup(""); menu_inputnumber(&ni_param); diff --git a/src/menu_android.c b/src/menu_android.c new file mode 100644 index 0000000..b5327df --- /dev/null +++ b/src/menu_android.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include "config.h" + +#include +#include + +#include "system.h" +#include "portab.h" +#include "menu.h" +#include "nact.h" + +#define STRING "Ljava/lang/String;" + +void menu_open(void) { + return; +} + +void menu_quitmenu_open(void) { + return; +} + +boolean menu_inputstring(INPUTSTRING_PARAM *p) { + static char buf[256]; + JNIEnv *env = SDL_AndroidGetJNIEnv(); + if ((*env)->PushLocalFrame(env, 16) < 0) { + WARNING("Failed to allocate JVM local references"); + return FALSE; + } + + jstring msg = (*env)->NewStringUTF(env, p->title); + jstring oldstring = (*env)->NewStringUTF(env, p->oldstring); + if (!msg || !oldstring) { + WARNING("Failed to allocate a string"); + (*env)->PopLocalFrame(env, NULL); + return FALSE; + } + + jobject context = SDL_AndroidGetActivity(); + jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), + "inputString", "(" STRING STRING ")" STRING); + jstring newstring = (jstring)(*env)->CallObjectMethod(env, context, mid, msg, oldstring); + if (newstring) { + const char *newstr_utf8 = (*env)->GetStringUTFChars(env, newstring, NULL); + strcpy(buf, newstr_utf8); + (*env)->ReleaseStringUTFChars(env, newstring, newstr_utf8); + (*env)->PopLocalFrame(env, NULL); + p->newstring = buf; + return TRUE; + } + + (*env)->PopLocalFrame(env, NULL); + p->newstring = p->oldstring; + return FALSE; +} + +boolean menu_inputstring2(INPUTSTRING_PARAM *p) { + p->newstring = p->oldstring; + return TRUE; +} + +boolean menu_inputnumber(INPUTNUM_PARAM *p) { + JNIEnv *env = SDL_AndroidGetJNIEnv(); + if ((*env)->PushLocalFrame(env, 16) < 0) { + WARNING("Failed to allocate JVM local references"); + return FALSE; + } + + jstring msg = (*env)->NewStringUTF(env, p->title); + if (!msg) { + WARNING("Failed to allocate a string"); + (*env)->PopLocalFrame(env, NULL); + return FALSE; + } + + jobject context = SDL_AndroidGetActivity(); + jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), + "inputNumber", "(" STRING "III)I"); + p->value = (*env)->CallIntMethod(env, context, mid, msg, p->min, p->max, p->def); + + (*env)->PopLocalFrame(env, NULL); + return TRUE; +} + +void menu_msgbox_open(char *msg) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, + nact->game_title_utf8, msg, NULL); +} + +void menu_init(void) { + return; +} + +void menu_gtkmainiteration() { + return; +}