mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Add settings, games and emulation activities
This commit is contained in:
@@ -49,7 +49,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
|
||||
const std::vector<const char*> kRequiredDeviceExtensions =
|
||||
{
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
#if !__ANDROID__
|
||||
VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME
|
||||
#endif
|
||||
}; // Intel doesnt support VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
|
||||
@@ -442,7 +444,7 @@ VulkanRenderer::VulkanRenderer()
|
||||
deviceFeatures.imageCubeArray = VK_TRUE;
|
||||
#if !BOOST_OS_MACOS
|
||||
deviceFeatures.geometryShader = VK_TRUE;
|
||||
deviceFeatures.logicOp = VK_TRUE;
|
||||
// deviceFeatures.logicOp = VK_TRUE;
|
||||
#endif
|
||||
deviceFeatures.occlusionQueryPrecise = VK_TRUE;
|
||||
deviceFeatures.depthClamp = VK_TRUE;
|
||||
|
||||
@@ -13,8 +13,7 @@ android {
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
ndk {
|
||||
// abiFilters("x86_64", "arm64-v8a")
|
||||
abiFilters("x86_64")
|
||||
abiFilters("x86_64", "arm64-v8a")
|
||||
}
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -48,7 +47,7 @@ android {
|
||||
'-DENABLE_DISCORD_RPC=OFF',
|
||||
'-DENABLE_WAYLAND=OFF'
|
||||
)
|
||||
abiFilters("x86_64")
|
||||
abiFilters("x86_64", "arm64-v8a")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +60,9 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.5.3'
|
||||
implementation 'androidx.navigation:navigation-ui:2.5.3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
|
||||
@@ -3,18 +3,29 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name="info.cemu.Cemu.CemuApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:isGame="true"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Cemu"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".EmulationActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="userLandscape" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Cemu">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -2,11 +2,15 @@ add_library(
|
||||
CemuAndroid
|
||||
SHARED
|
||||
native-lib.cpp
|
||||
JNIUtils.cpp
|
||||
JNIUtils.h
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
CemuAndroid
|
||||
PRIVATE
|
||||
-landroid
|
||||
CemuCommon
|
||||
CemuBin
|
||||
CemuAndroidGui
|
||||
)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "JNIUtils.h"
|
||||
|
||||
namespace JNIUtils
|
||||
{
|
||||
JavaVM *g_jvm = nullptr;
|
||||
};
|
||||
|
||||
std::string JNIUtils::JStringToString(JNIEnv *env, jstring jstr)
|
||||
{
|
||||
const char *c_str = env->GetStringUTFChars(jstr, nullptr);
|
||||
std::string str(c_str);
|
||||
env->ReleaseStringUTFChars(jstr, c_str);
|
||||
return str;
|
||||
}
|
||||
|
||||
jobject JNIUtils::createJavaStringArrayList(JNIEnv *env, const std::vector<std::string> &strings)
|
||||
{
|
||||
jclass clsArrayList = env->FindClass("java/util/ArrayList");
|
||||
jmethodID arrayListConstructor = env->GetMethodID(clsArrayList, "<init>", "()V");
|
||||
jobject arrayListObject = env->NewObject(clsArrayList, arrayListConstructor);
|
||||
jmethodID addMethod = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");
|
||||
env->DeleteLocalRef(clsArrayList);
|
||||
|
||||
for (const auto &string : strings)
|
||||
{
|
||||
jstring element = env->NewStringUTF(string.c_str());
|
||||
env->CallBooleanMethod(arrayListObject, addMethod, element);
|
||||
env->DeleteLocalRef(element);
|
||||
}
|
||||
return arrayListObject;
|
||||
}
|
||||
|
||||
JNIUtils::Scopedjobject JNIUtils::getEnumValue(JNIEnv *env, const std::string &enumClassName, const std::string &enumName)
|
||||
{
|
||||
jclass enumClass = env->FindClass(enumClassName.c_str());
|
||||
jfieldID fieldID = env->GetStaticFieldID(enumClass, enumName.c_str(), ("L" + enumClassName + ";").c_str());
|
||||
jobject enumValue = env->GetStaticObjectField(enumClass, fieldID);
|
||||
env->DeleteLocalRef(enumClass);
|
||||
Scopedjobject enumObj = Scopedjobject(enumValue);
|
||||
env->DeleteLocalRef(enumValue);
|
||||
return enumObj;
|
||||
}
|
||||
|
||||
jobject JNIUtils::createArrayList(JNIEnv *env, const std::vector<jobject> &objects)
|
||||
{
|
||||
static Scopedjclass listClass = Scopedjclass("java/util/ArrayList");
|
||||
static jmethodID listConstructor = env->GetMethodID(*listClass, "<init>", "()V");
|
||||
static jmethodID listAdd = env->GetMethodID(*listClass, "add", "(Ljava/lang/Object;)Z");
|
||||
|
||||
jobject arrayList = env->NewObject(*listClass, listConstructor);
|
||||
for (auto &&obj : objects)
|
||||
env->CallBooleanMethod(arrayList, listAdd, obj);
|
||||
return arrayList;
|
||||
}
|
||||
|
||||
jobject JNIUtils::createJavaStringArrayList(JNIEnv *env, const std::vector<std::wstring> &strings)
|
||||
{
|
||||
jclass arrayListClass = env->FindClass("java/util/ArrayList");
|
||||
jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
|
||||
jobject arrayListObject = env->NewObject(arrayListClass, arrayListConstructor);
|
||||
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
|
||||
for (const auto &string : strings)
|
||||
{
|
||||
jstring element = env->NewString((jchar *)string.c_str(), string.length());
|
||||
env->CallBooleanMethod(arrayListObject, addMethod, element);
|
||||
env->DeleteLocalRef(element);
|
||||
}
|
||||
return arrayListObject;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <jni.h>
|
||||
|
||||
namespace JNIUtils
|
||||
{
|
||||
extern JavaVM *g_jvm;
|
||||
|
||||
std::string JStringToString(JNIEnv *env, jstring jstr);
|
||||
|
||||
jobject createJavaStringArrayList(JNIEnv *env, const std::vector<std::string> &stringList);
|
||||
|
||||
jobject createJavaStringArrayList(JNIEnv *env, const std::vector<std::wstring> &stringList);
|
||||
|
||||
class ScopedJNIENV
|
||||
{
|
||||
public:
|
||||
ScopedJNIENV()
|
||||
{
|
||||
jint result = g_jvm->GetEnv((void **)&m_env, JNI_VERSION_1_6);
|
||||
if (result == JNI_EDETACHED)
|
||||
{
|
||||
JavaVMAttachArgs args;
|
||||
args.version = JNI_VERSION_1_6;
|
||||
args.name = nullptr;
|
||||
args.group = nullptr;
|
||||
result = g_jvm->AttachCurrentThread(&m_env, &args);
|
||||
if (result == JNI_OK)
|
||||
m_threadWasAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEnv *&operator*() { return m_env; }
|
||||
|
||||
JNIEnv *operator->() { return m_env; }
|
||||
|
||||
~ScopedJNIENV()
|
||||
{
|
||||
if (m_threadWasAttached)
|
||||
g_jvm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
private:
|
||||
JNIEnv *m_env = nullptr;
|
||||
bool m_threadWasAttached = false;
|
||||
};
|
||||
|
||||
class Scopedjobject
|
||||
{
|
||||
public:
|
||||
Scopedjobject() = default;
|
||||
|
||||
Scopedjobject(Scopedjobject &&other)
|
||||
{
|
||||
this->m_jobject = other.m_jobject;
|
||||
other.m_jobject = nullptr;
|
||||
}
|
||||
void deleteRef()
|
||||
{
|
||||
if (m_jobject)
|
||||
{
|
||||
ScopedJNIENV()->DeleteGlobalRef(m_jobject);
|
||||
m_jobject = nullptr;
|
||||
}
|
||||
}
|
||||
Scopedjobject &operator=(Scopedjobject &&other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
deleteRef();
|
||||
m_jobject = other.m_jobject;
|
||||
other.m_jobject = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
jobject &operator*() { return m_jobject; }
|
||||
|
||||
Scopedjobject(jobject obj)
|
||||
{
|
||||
if (obj)
|
||||
m_jobject = ScopedJNIENV()->NewGlobalRef(obj);
|
||||
}
|
||||
|
||||
~Scopedjobject()
|
||||
{
|
||||
deleteRef();
|
||||
}
|
||||
|
||||
bool isValid() const { return m_jobject; }
|
||||
|
||||
private:
|
||||
jobject m_jobject = nullptr;
|
||||
};
|
||||
|
||||
class Scopedjclass
|
||||
{
|
||||
public:
|
||||
Scopedjclass() = default;
|
||||
|
||||
Scopedjclass(Scopedjclass &&other)
|
||||
{
|
||||
this->m_jclass = other.m_jclass;
|
||||
other.m_jclass = nullptr;
|
||||
}
|
||||
|
||||
Scopedjclass &operator=(Scopedjclass &&other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (m_jclass)
|
||||
ScopedJNIENV()->DeleteGlobalRef(m_jclass);
|
||||
m_jclass = other.m_jclass;
|
||||
other.m_jclass = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Scopedjclass(const std::string &className)
|
||||
{
|
||||
ScopedJNIENV scopedEnv;
|
||||
jobject tempObj = scopedEnv->FindClass(className.c_str());
|
||||
m_jclass = static_cast<jclass>(scopedEnv->NewGlobalRef(tempObj));
|
||||
scopedEnv->DeleteLocalRef(tempObj);
|
||||
}
|
||||
|
||||
~Scopedjclass()
|
||||
{
|
||||
if (m_jclass)
|
||||
ScopedJNIENV()->DeleteGlobalRef(m_jclass);
|
||||
}
|
||||
|
||||
bool isValid() const { return m_jclass; }
|
||||
|
||||
jclass &operator*() { return m_jclass; }
|
||||
|
||||
private:
|
||||
jclass m_jclass = nullptr;
|
||||
};
|
||||
|
||||
Scopedjobject getEnumValue(JNIEnv *env, const std::string &enumClassName, const std::string &enumName);
|
||||
jobject createArrayList(JNIEnv *env, const std::vector<jobject> &objects);
|
||||
} // namespace JNIUtils
|
||||
@@ -1,10 +1,183 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <android/native_window.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <AndroidGui/GameTitleLoader.h>
|
||||
#include <AndroidGui/GameIconLoader.h>
|
||||
#include <AndroidGui/AndroidGuiWrapper.h>
|
||||
#include <Cafe/HW/Latte/Renderer/Renderer.h>
|
||||
#include <Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h>
|
||||
#include "AndroidGui/Utils.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "config/NetworkSettings.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_com_example_myapplication_MainActivity_stringFromJNI(
|
||||
JNIEnv *env,
|
||||
jobject /* this */) {
|
||||
std::string hello = "Hello from C++";
|
||||
return env->NewStringUTF(hello.c_str());
|
||||
|
||||
#include "JNIUtils.h"
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIUtils::g_jvm = vm;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setSurface(JNIEnv *env, jclass clazz, jobject surface,
|
||||
jboolean is_main_canvas) {
|
||||
WindowHandleInfo *windowHandleInfo;
|
||||
if (is_main_canvas)
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_main;
|
||||
else
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_pad;
|
||||
if (windowHandleInfo->handle) {
|
||||
ANativeWindow_release(static_cast<ANativeWindow *>(windowHandleInfo->handle));
|
||||
windowHandleInfo->handle = nullptr;
|
||||
}
|
||||
if (surface)
|
||||
windowHandleInfo->handle = ANativeWindow_fromSurface(env, surface);
|
||||
gui_getWindowInfo().window_main.handle = windowHandleInfo->handle;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setSurfaceSize(JNIEnv *env, jclass clazz, jint width, jint height,
|
||||
jboolean is_main_canvas) {
|
||||
WindowHandleInfo *windowHandleInfo;
|
||||
if (is_main_canvas)
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_main;
|
||||
else
|
||||
windowHandleInfo = &gui_getWindowInfo().canvas_pad;
|
||||
gui_getWindowInfo().width = width;
|
||||
gui_getWindowInfo().height = height;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_startGame(JNIEnv *env, jclass clazz, jlong title_id) {
|
||||
gui_startGame(*reinterpret_cast<TitleId *>(&title_id));
|
||||
}
|
||||
|
||||
std::shared_ptr<GameTitleLoadedCallback> g_gameTitleLoadedCallback;
|
||||
|
||||
class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback {
|
||||
jmethodID m_onGameTitleLoadedMID;
|
||||
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
|
||||
public:
|
||||
AndroidGameTitleLoadedCallback(jmethodID onGameTitleLoadedMID,
|
||||
jobject gameTitleLoadedCallbackObj)
|
||||
: m_onGameTitleLoadedMID(onGameTitleLoadedMID),
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
|
||||
|
||||
void onTitleLoaded(const Game &game) override {
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
jstring name = env->NewStringUTF(game.name.c_str());
|
||||
jlong titleId = *reinterpret_cast<const jlong *>(&game.titleId);
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, titleId, name);
|
||||
env->DeleteLocalRef(name);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setGameTitleLoadedCallback(JNIEnv *env, jclass clazz,
|
||||
jobject game_title_loaded_callback) {
|
||||
jclass gameTitleLoadedCallbackClass = env->GetObjectClass(game_title_loaded_callback);
|
||||
jmethodID onGameTitleLoadedMID = env->GetMethodID(gameTitleLoadedCallbackClass,
|
||||
"onGameTitleLoaded",
|
||||
"(JLjava/lang/String;)V");
|
||||
env->DeleteLocalRef(gameTitleLoadedCallbackClass);
|
||||
g_gameTitleLoadedCallback = std::make_shared<AndroidGameTitleLoadedCallback>(
|
||||
onGameTitleLoadedMID,
|
||||
game_title_loaded_callback);
|
||||
gui_setOnGameTitleLoaded(g_gameTitleLoadedCallback);
|
||||
}
|
||||
|
||||
std::shared_ptr<GameIconLoadedCallback> g_gameIconLoadedCallback;
|
||||
|
||||
class AndroidGameIconLoadedCallback : public GameIconLoadedCallback {
|
||||
jmethodID m_onGameIconLoadedMID;
|
||||
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
|
||||
public:
|
||||
AndroidGameIconLoadedCallback(jmethodID onGameIconLoadedMID,
|
||||
jobject gameTitleLoadedCallbackObj)
|
||||
: m_onGameIconLoadedMID(onGameIconLoadedMID),
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
|
||||
|
||||
void onIconLoaded(TitleId titleId, const Image &iconData) override {
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
jlong jTitleId = *reinterpret_cast<jlong *>(&titleId);
|
||||
jintArray jIconData = env->NewIntArray(iconData.m_width * iconData.m_height);
|
||||
env->SetIntArrayRegion(jIconData, 0, iconData.m_width * iconData.m_height,
|
||||
reinterpret_cast<const jint *>(iconData.m_image));
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, jTitleId,
|
||||
jIconData, iconData.m_width, iconData.m_height);
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setGameIconLoadedCallback(JNIEnv *env, jclass clazz,
|
||||
jobject game_icon_loaded_callback) {
|
||||
jclass gameIconLoadedCallbackClass = env->GetObjectClass(game_icon_loaded_callback);
|
||||
jmethodID gameIconLoadedMID = env->GetMethodID(gameIconLoadedCallbackClass,
|
||||
"onGameIconLoaded",
|
||||
"(J[III)V");
|
||||
env->DeleteLocalRef(gameIconLoadedCallbackClass);
|
||||
g_gameIconLoadedCallback = std::make_shared<AndroidGameIconLoadedCallback>(gameIconLoadedMID,
|
||||
game_icon_loaded_callback);
|
||||
gui_setOnGameIconLoaded(g_gameIconLoadedCallback);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_requestGameIcon(JNIEnv *env, jclass clazz, jlong title_id) {
|
||||
TitleId titleId = *reinterpret_cast<TitleId *>(&title_id);
|
||||
gui_requestGameIcon(titleId);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_reloadGameTitles(JNIEnv *env, jclass clazz) {
|
||||
gui_reloadGameTitles();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_initializeActiveSettings(JNIEnv *env, jclass clazz,
|
||||
jstring data_path, jstring cache_path) {
|
||||
std::string dataPath = JNIUtils::JStringToString(env, data_path);
|
||||
std::string cachePath = JNIUtils::JStringToString(env, cache_path);
|
||||
ActiveSettings::LoadOnce({}, dataPath, dataPath, cachePath, dataPath);
|
||||
}
|
||||
|
||||
int mainEmulatorHLE();
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_initializeEmulation(JNIEnv *env, jclass clazz) {
|
||||
NetworkConfig::LoadOnce();
|
||||
mainEmulatorHLE();
|
||||
InputManager::instance().load();
|
||||
InitializeGlobalVulkan();
|
||||
createCemuDirectories();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_initializerRenderer(JNIEnv *env, jclass clazz) {
|
||||
g_renderer = std::make_unique<VulkanRenderer>();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_initializeRendererSurface(JNIEnv *env, jclass clazz,
|
||||
jboolean is_main_canvas) {
|
||||
int width, height;
|
||||
if (is_main_canvas)
|
||||
gui_getWindowPhysSize(width, height);
|
||||
else
|
||||
gui_getPadWindowPhysSize(width, height);
|
||||
VulkanRenderer::GetInstance()->InitializeSurface(
|
||||
{width, height},
|
||||
is_main_canvas);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class CemuApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
NativeLibrary.initializeActiveSettings(getExternalFilesDir(null).getAbsoluteFile().toString(), getCacheDir().toString());
|
||||
NativeLibrary.initializeEmulation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsAnimationCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import info.cemu.Cemu.databinding.ActivityEmulationBinding;
|
||||
import info.cemu.Cemu.databinding.ActivityMainBinding;
|
||||
|
||||
public class EmulationActivity extends AppCompatActivity {
|
||||
|
||||
public static final String GAME_TITLE_ID = "GameTitleId";
|
||||
long gameTitleId;
|
||||
ActivityEmulationBinding binding;
|
||||
EmulationFragment emulationFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
gameTitleId = extras.getLong(GAME_TITLE_ID);
|
||||
}
|
||||
setFullscreen();
|
||||
binding = ActivityEmulationBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
|
||||
if (emulationFragment == null) {
|
||||
emulationFragment = EmulationFragment.newInstance(gameTitleId);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.emulation_frame, emulationFragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
void setFullscreen() {
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||
WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars());
|
||||
controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import info.cemu.Cemu.databinding.FragmentEmulationBinding;
|
||||
import info.cemu.Cemu.databinding.FragmentGamesBinding;
|
||||
|
||||
public class EmulationFragment extends Fragment implements SurfaceHolder.Callback {
|
||||
FragmentEmulationBinding binding;
|
||||
|
||||
public EmulationFragment() {
|
||||
}
|
||||
|
||||
static EmulationFragment newInstance(long gameTitleId) {
|
||||
EmulationFragment emulationFragment = new EmulationFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putLong(EmulationActivity.GAME_TITLE_ID, gameTitleId);
|
||||
emulationFragment.setArguments(arguments);
|
||||
return emulationFragment;
|
||||
}
|
||||
|
||||
long gameTitleId;
|
||||
boolean isRunning;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
gameTitleId = arguments.getLong(EmulationActivity.GAME_TITLE_ID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentEmulationBinding.inflate(inflater, container, false);
|
||||
SurfaceView mainCanvas = binding.mainCanvas;
|
||||
mainCanvas.getHolder().addCallback(this);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
if (!isRunning) {
|
||||
isRunning = true;
|
||||
NativeLibrary.initializerRenderer();
|
||||
NativeLibrary.initializeRendererSurface(true);
|
||||
NativeLibrary.startGame(gameTitleId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
|
||||
NativeLibrary.setSurface(surfaceHolder.getSurface(), true);
|
||||
NativeLibrary.setSurfaceSize(width, height, true);
|
||||
startGame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public class Game {
|
||||
Long titleId;
|
||||
String title;
|
||||
Bitmap icon;
|
||||
|
||||
public Game(Long titleId, String title) {
|
||||
this.titleId = titleId;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setIconData(int[] colors, int width, int height) {
|
||||
icon = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
public Bitmap getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public Long getTitleId() {
|
||||
return titleId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class GameAdapter extends RecyclerView.Adapter<GameAdapter.ViewHolder> {
|
||||
HashMap<Long, Game> gameInfoMap;
|
||||
Vector<Long> gameTitleIds;
|
||||
private final GameTitleClickAction gameTitleClickAction;
|
||||
|
||||
public interface GameTitleClickAction {
|
||||
void action(long titleId);
|
||||
}
|
||||
|
||||
public GameAdapter(GameTitleClickAction gameTitleClickAction) {
|
||||
super();
|
||||
this.gameTitleClickAction = gameTitleClickAction;
|
||||
gameTitleIds = new Vector<>();
|
||||
gameInfoMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GameAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_game, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GameAdapter.ViewHolder holder, int position) {
|
||||
Game game = gameInfoMap.get(gameTitleIds.get(position));
|
||||
if (game != null) {
|
||||
holder.icon.setImageBitmap(game.getIcon());
|
||||
holder.text.setText(game.getTitle());
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
long titleId = game.titleId;
|
||||
gameTitleClickAction.action(titleId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return gameTitleIds.size();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
int itemCount = gameTitleIds.size();
|
||||
notifyItemRangeRemoved(0, itemCount);
|
||||
gameInfoMap.clear();
|
||||
gameTitleIds.clear();
|
||||
}
|
||||
|
||||
void addGameInfo(long titleId, String title) {
|
||||
gameTitleIds.addElement(titleId);
|
||||
gameInfoMap.put(titleId, new Game(titleId, title));
|
||||
notifyItemInserted(gameTitleIds.size() - 1);
|
||||
}
|
||||
|
||||
void setGameIcon(long titleId, int[] colors, int width, int height) {
|
||||
Game game = gameInfoMap.get(titleId);
|
||||
if (game != null) {
|
||||
game.setIconData(colors, width, height);
|
||||
notifyItemChanged(gameTitleIds.indexOf(titleId));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView icon;
|
||||
TextView text;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.game_icon);
|
||||
text = itemView.findViewById(R.id.game_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import info.cemu.Cemu.databinding.FragmentGamesBinding;
|
||||
|
||||
public class GamesFragment extends Fragment {
|
||||
FragmentGamesBinding binding;
|
||||
GameAdapter gameAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentGamesBinding.inflate(inflater, container, false);
|
||||
RecyclerView recyclerView = binding.gamesRecyclerView;
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
View rootView = binding.getRoot();
|
||||
gameAdapter = new GameAdapter(titleId -> {
|
||||
Intent intent = new Intent(getContext(), EmulationActivity.class);
|
||||
intent.putExtra(EmulationActivity.GAME_TITLE_ID, titleId);
|
||||
startActivity(intent);
|
||||
});
|
||||
binding.gamesSwipeRefresh.setOnRefreshListener(() -> {
|
||||
gameAdapter.clear();
|
||||
NativeLibrary.reloadGameTitles();
|
||||
binding.gamesSwipeRefresh.setRefreshing(false);
|
||||
});
|
||||
recyclerView.setAdapter(gameAdapter);
|
||||
NativeLibrary.setGameTitleLoadedCallback((titleId, title) -> {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
gameAdapter.addGameInfo(titleId, title);
|
||||
NativeLibrary.requestGameIcon(titleId);
|
||||
});
|
||||
});
|
||||
NativeLibrary.setGameIconLoadedCallback((titleId, colors, width, height) -> {
|
||||
requireActivity().runOnUiThread(() -> gameAdapter.setGameIcon(titleId, colors, width, height));
|
||||
});
|
||||
NativeLibrary.reloadGameTitles();
|
||||
return rootView;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +1,54 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import info.cemu.Cemu.databinding.ActivityMainBinding;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
static {
|
||||
System.loadLibrary("CemuAndroid");
|
||||
}
|
||||
|
||||
private ActivityMainBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
// Example of a call to a native method
|
||||
TextView tv = binding.sampleText;
|
||||
tv.setText(stringFromJNI());
|
||||
binding.fab.setOnClickListener(
|
||||
view -> {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A native method that is implemented by the 'Cemu' native library,
|
||||
* which is packaged with this application.
|
||||
*/
|
||||
public native String stringFromJNI();
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_settings) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.Surface;
|
||||
|
||||
public class NativeLibrary {
|
||||
static {
|
||||
System.loadLibrary("CemuAndroid");
|
||||
}
|
||||
|
||||
public static native void setSurface(Surface surface, boolean isMainCanvas);
|
||||
|
||||
public static native void setSurfaceSize(int width, int height, boolean isMainCanvas);
|
||||
|
||||
public static native void initializerRenderer();
|
||||
|
||||
public static native void initializeRendererSurface(boolean isMainCanvas);
|
||||
|
||||
public static native void startGame(long titleId);
|
||||
|
||||
|
||||
public interface GameTitleLoadedCallback {
|
||||
void onGameTitleLoaded(long titleId, String title);
|
||||
}
|
||||
|
||||
public static native void setGameTitleLoadedCallback(GameTitleLoadedCallback gameTitleLoadedCallback);
|
||||
|
||||
public interface GameIconLoadedCallback {
|
||||
void onGameIconLoaded(long titleId, int[] colors, int width, int height);
|
||||
}
|
||||
|
||||
public static native void setGameIconLoadedCallback(GameIconLoadedCallback gameIconLoadedCallback);
|
||||
|
||||
public static native void requestGameIcon(long titleId);
|
||||
|
||||
public static native void reloadGameTitles();
|
||||
|
||||
public static native void initializeActiveSettings(String dataPath, String cachePath);
|
||||
|
||||
public static native void initializeEmulation();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import info.cemu.Cemu.databinding.ActivitySettingsBinding;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
|
||||
private ActivitySettingsBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
setSupportActionBar(binding.toolbarSettings);
|
||||
Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
|
||||
getSupportFragmentManager().popBackStack();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SpinnerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import info.cemu.Cemu.databinding.FragmentSettingsBinding;
|
||||
|
||||
import info.cemu.Cemu.databinding.SettingsCheckboxBinding;
|
||||
import info.cemu.Cemu.databinding.SettingsHeaderBinding;
|
||||
import info.cemu.Cemu.databinding.SettingsCheckboxBinding;
|
||||
import info.cemu.Cemu.databinding.SettingsSpinnerBinding;
|
||||
|
||||
public class SettingsFragment extends Fragment {
|
||||
protected FragmentSettingsBinding binding;
|
||||
|
||||
public SettingsFragment() {
|
||||
}
|
||||
|
||||
// public abstract int getSettingsNameId();
|
||||
|
||||
protected interface OnCheckBoxStateListener {
|
||||
void onStateChanged(boolean checked);
|
||||
}
|
||||
|
||||
protected interface OnItemSelectedPositionListener {
|
||||
void onItemSelected(int position);
|
||||
}
|
||||
|
||||
protected void addCheckBox(String title, String description, boolean initialValue, OnCheckBoxStateListener checkBoxStateListener) {
|
||||
View checkboxLayout = getLayoutInflater().inflate(R.layout.settings_spinner, null);
|
||||
binding.settingsLayout.addView(checkboxLayout);
|
||||
SettingsCheckboxBinding checkboxBinding = SettingsCheckboxBinding.bind(checkboxLayout);
|
||||
checkboxBinding.textviewCheckboxName.setText(title);
|
||||
checkboxBinding.checkbox.setChecked(initialValue);
|
||||
checkboxBinding.textviewCheckboxDescription.setText(description);
|
||||
checkboxLayout.setOnClickListener(v -> {
|
||||
boolean isChecked = !checkboxBinding.checkbox.isChecked();
|
||||
checkboxBinding.checkbox.setChecked(isChecked);
|
||||
checkBoxStateListener.onStateChanged(isChecked);
|
||||
});
|
||||
}
|
||||
|
||||
// protected void addSeparator() {
|
||||
// View divider = getLayoutInflater().inflate(R.layout.divider_layout, null);
|
||||
// binding.settingsLayout.addView(divider);
|
||||
// }
|
||||
|
||||
protected void addHeader(String title) {
|
||||
View headerLayout = getLayoutInflater().inflate(R.layout.settings_header, null);
|
||||
binding.settingsLayout.addView(headerLayout);
|
||||
SettingsHeaderBinding headerBinding = SettingsHeaderBinding.bind(headerLayout);
|
||||
headerBinding.textviewHeaderName.setText(title);
|
||||
}
|
||||
|
||||
protected void addSpinner(String title, List<String> items, int initialPosition, OnItemSelectedPositionListener itemSelectedPositionListener) {
|
||||
View spinnerLayout = getLayoutInflater().inflate(R.layout.settings_spinner, null);
|
||||
binding.settingsLayout.addView(spinnerLayout);
|
||||
SettingsSpinnerBinding spinnerBinding = SettingsSpinnerBinding.bind(spinnerLayout);
|
||||
spinnerBinding.textviewSpinnerName.setText(title);
|
||||
spinnerLayout.setOnClickListener(v -> spinnerBinding.spinner.performClick());
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, items);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerBinding.spinner.setAdapter(adapter);
|
||||
spinnerBinding.spinner.setSelection(initialPosition);
|
||||
spinnerBinding.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
itemSelectedPositionListener.onItemSelected(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentSettingsBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M388,880L368,754Q349,747 328,735Q307,723 291,710L173,764L80,600L188,521Q186,512 185.5,500.5Q185,489 185,480Q185,471 185.5,459.5Q186,448 188,439L80,360L173,196L291,250Q307,237 328,225Q349,213 368,207L388,80L572,80L592,206Q611,213 632.5,224.5Q654,236 669,250L787,196L880,360L772,437Q774,447 774.5,458.5Q775,470 775,480Q775,490 774.5,501Q774,512 772,522L880,600L787,764L669,710Q653,723 632.5,735.5Q612,748 592,754L572,880L388,880ZM480,610Q534,610 572,572Q610,534 610,480Q610,426 572,388Q534,350 480,350Q426,350 388,388Q350,426 350,480Q350,534 388,572Q426,610 480,610ZM480,550Q451,550 430.5,529.5Q410,509 410,480Q410,451 430.5,430.5Q451,410 480,410Q509,410 529.5,430.5Q550,451 550,480Q550,509 529.5,529.5Q509,550 480,550ZM480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480ZM436,820L524,820L538,708Q571,700 600.5,683Q630,666 654,642L760,688L800,616L706,547Q710,530 712.5,513.5Q715,497 715,480Q715,463 713,446.5Q711,430 706,413L800,344L760,272L654,318Q631,292 602,274.5Q573,257 538,252L524,140L436,140L422,252Q388,259 358.5,276Q329,293 306,318L200,272L160,344L254,413Q250,430 247.5,446.5Q245,463 245,480Q245,497 247.5,513.5Q250,530 254,547L160,616L200,688L306,642Q330,666 359.5,683Q389,700 422,708L436,820Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true"
|
||||
tools:context=".EmulationActivity">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/emulation_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,19 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sample_text"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment_content_main"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/settings_fab_margin"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="@string/settings"
|
||||
app:srcCompat="@drawable/ic_settings" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user