Add settings, games and emulation activities

This commit is contained in:
SSimco
2023-06-12 15:49:48 +03:00
parent 12e7f9956c
commit 785a37e503
50 changed files with 1480 additions and 86 deletions
@@ -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;
+5 -3
View File
@@ -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'
+12 -1
View File
@@ -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
)
+69
View File
@@ -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;
}
+143
View File
@@ -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
+179 -6
View File
@@ -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