mirror of
https://github.com/SSimco/Cemu.git
synced 2024-11-23 05:19:40 +00:00
Added context menu for game list
This commit is contained in:
parent
6c0dc45f86
commit
1d17abd9d9
@ -27,14 +27,18 @@ public:
|
||||
[[nodiscard]] const std::optional<std::string>& GetGameName() const { return m_gameName; }
|
||||
|
||||
[[nodiscard]] const std::optional<bool>& ShouldLoadSharedLibraries() const { return m_loadSharedLibraries; }
|
||||
void SetShouldLoadSharedLibraries(bool shouldLoadSharedLibraries) { m_loadSharedLibraries = shouldLoadSharedLibraries; }
|
||||
[[nodiscard]] bool StartWithGamepadView() const { return m_startWithPadView; }
|
||||
|
||||
[[nodiscard]] const std::optional<GraphicAPI>& GetGraphicsAPI() const { return m_graphics_api; }
|
||||
[[nodiscard]] const AccurateShaderMulOption& GetAccurateShaderMul() const { return m_accurateShaderMul; }
|
||||
void SetAccurateShaderMul(AccurateShaderMulOption accurateShaderMulOption) { m_accurateShaderMul = accurateShaderMulOption; }
|
||||
[[nodiscard]] const std::optional<PrecompiledShaderOption>& GetPrecompiledShadersState() const { return m_precompiledShaders; }
|
||||
|
||||
[[nodiscard]] uint32 GetThreadQuantum() const { return m_threadQuantum; }
|
||||
void SetThreadQuantum(uint32 threadQuantum){ m_threadQuantum = threadQuantum; }
|
||||
[[nodiscard]] const std::optional<CPUMode>& GetCPUMode() const { return m_cpuMode; }
|
||||
void SetCPUMode(CPUMode cpuMode) { m_cpuMode = cpuMode; }
|
||||
|
||||
[[nodiscard]] bool IsAudioDisabled() const { return m_disableAudio; }
|
||||
|
||||
|
@ -2,34 +2,68 @@
|
||||
|
||||
#include "GameTitleLoader.h"
|
||||
#include "JNIUtils.h"
|
||||
#include <android/bitmap.h>
|
||||
|
||||
class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
|
||||
{
|
||||
jmethodID m_onGameTitleLoadedMID;
|
||||
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
|
||||
jmethodID m_gameConstructorMID;
|
||||
JNIUtils::Scopedjclass m_gamejclass{"info/cemu/Cemu/nativeinterface/NativeGameTitles$Game"};
|
||||
jmethodID m_createBitmapMID;
|
||||
JNIUtils::Scopedjclass m_bitmapClass{"android/graphics/Bitmap"};
|
||||
JNIUtils::Scopedjobject m_bitmapFormat;
|
||||
|
||||
public:
|
||||
AndroidGameTitleLoadedCallback(jmethodID onGameTitleLoadedMID, jobject gameTitleLoadedCallbackObj)
|
||||
: m_onGameTitleLoadedMID(onGameTitleLoadedMID),
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
|
||||
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj)
|
||||
{
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
m_bitmapFormat = JNIUtils::getEnumValue(*env, "android/graphics/Bitmap$Config", "ARGB_8888");
|
||||
m_gameConstructorMID = env->GetMethodID(*m_gamejclass, "<init>", "(JLjava/lang/String;Ljava/lang/String;SSISSSIZLandroid/graphics/Bitmap;)V");
|
||||
m_createBitmapMID = env->GetStaticMethodID(*m_bitmapClass, "createBitmap", "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
}
|
||||
|
||||
void onTitleLoaded(const Game& game, const std::shared_ptr<Image>& icon) override
|
||||
{
|
||||
JNIUtils::ScopedJNIENV env;
|
||||
static JNIUtils::ScopedJNIENV env;
|
||||
jstring name = env->NewStringUTF(game.name.c_str());
|
||||
jstring path = game.path.has_value() ? env->NewStringUTF(game.path->c_str()) : nullptr;
|
||||
int width = -1, height = -1;
|
||||
jintArray jIconData = nullptr;
|
||||
jobject bitmap = nullptr;
|
||||
sint32 lastPlayedYear = 0, lastPlayedMonth = 0, lastPlayedDay = 0;
|
||||
if (game.lastPlayed.has_value())
|
||||
{
|
||||
lastPlayedYear = static_cast<int>(game.lastPlayed->year());
|
||||
lastPlayedMonth = static_cast<unsigned int>(game.lastPlayed->month());
|
||||
lastPlayedDay = static_cast<unsigned int>(game.lastPlayed->day());
|
||||
}
|
||||
if (icon)
|
||||
{
|
||||
width = icon->m_width;
|
||||
height = icon->m_height;
|
||||
jIconData = env->NewIntArray(width * height);
|
||||
env->SetIntArrayRegion(jIconData, 0, width * height, reinterpret_cast<const jint*>(icon->intColors()));
|
||||
}
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, path, name, jIconData, width, height);
|
||||
if (jIconData != nullptr)
|
||||
jintArray jIconData = env->NewIntArray(icon->m_width * icon->m_height);
|
||||
env->SetIntArrayRegion(jIconData, 0, icon->m_width * icon->m_height, icon->m_colors);
|
||||
bitmap = env->CallStaticObjectMethod(*m_bitmapClass, m_createBitmapMID, jIconData, icon->m_width, icon->m_height, *m_bitmapFormat);
|
||||
env->DeleteLocalRef(jIconData);
|
||||
}
|
||||
jobject gamejobject = env->NewObject(
|
||||
*m_gamejclass,
|
||||
m_gameConstructorMID,
|
||||
game.titleId,
|
||||
path,
|
||||
name,
|
||||
game.version,
|
||||
game.dlc,
|
||||
static_cast<sint32>(game.region),
|
||||
lastPlayedYear,
|
||||
lastPlayedMonth,
|
||||
lastPlayedDay,
|
||||
game.minutesPlayed,
|
||||
game.isFavorite,
|
||||
bitmap);
|
||||
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, gamejobject);
|
||||
env->DeleteLocalRef(gamejobject);
|
||||
if (bitmap != nullptr)
|
||||
env->DeleteLocalRef(bitmap);
|
||||
if (path != nullptr)
|
||||
env->DeleteLocalRef(path);
|
||||
env->DeleteLocalRef(name);
|
||||
|
@ -56,6 +56,7 @@ GameTitleLoader::~GameTitleLoader()
|
||||
|
||||
void GameTitleLoader::titleRefresh(TitleId titleId)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId);
|
||||
if (!gameInfo.IsValid())
|
||||
{
|
||||
@ -74,26 +75,28 @@ void GameTitleLoader::titleRefresh(TitleId titleId)
|
||||
game.titleId = baseTitleId;
|
||||
if (titleInfo.has_value())
|
||||
game.path = titleInfo->GetPath();
|
||||
game.isFavorite = GetConfig().IsGameListFavorite(baseTitleId);
|
||||
game.name = getNameByTitleId(baseTitleId, titleInfo);
|
||||
game.version = gameInfo.GetVersion();
|
||||
game.region = gameInfo.GetRegion();
|
||||
game.dlc = gameInfo.GetAOCVersion();
|
||||
std::shared_ptr<Image> icon = loadIcon(baseTitleId, titleInfo);
|
||||
if (gameInfo.HasAOC())
|
||||
if (!isNewEntry)
|
||||
{
|
||||
game.dlc = gameInfo.GetAOCVersion();
|
||||
// TOOD: update?
|
||||
return;
|
||||
}
|
||||
if (isNewEntry)
|
||||
iosu::pdm::GameListStat playTimeStat{};
|
||||
if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat))
|
||||
{
|
||||
iosu::pdm::GameListStat playTimeStat{};
|
||||
if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat))
|
||||
game.secondsPlayed = playTimeStat.numMinutesPlayed * 60;
|
||||
if (m_gameTitleLoadedCallback)
|
||||
m_gameTitleLoadedCallback->onTitleLoaded(game, icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Update
|
||||
game.minutesPlayed = playTimeStat.numMinutesPlayed;
|
||||
if (playTimeStat.last_played.year != 0)
|
||||
{
|
||||
game.lastPlayed = year_month_day(year(playTimeStat.last_played.year), month(playTimeStat.last_played.month), day(playTimeStat.last_played.day));
|
||||
}
|
||||
}
|
||||
if (m_gameTitleLoadedCallback)
|
||||
m_gameTitleLoadedCallback->onTitleLoaded(game, icon);
|
||||
}
|
||||
|
||||
void GameTitleLoader::loadGameTitles()
|
||||
|
@ -9,10 +9,12 @@ struct Game
|
||||
{
|
||||
std::string name;
|
||||
std::optional<fs::path> path;
|
||||
uint32 secondsPlayed;
|
||||
uint16 dlc;
|
||||
bool isFavorite;
|
||||
uint16 version;
|
||||
uint16 dlc;
|
||||
TitleId titleId;
|
||||
std::optional<std::chrono::year_month_day> lastPlayed;
|
||||
uint32 minutesPlayed;
|
||||
CafeConsoleRegion region;
|
||||
};
|
||||
|
||||
|
@ -7,44 +7,39 @@
|
||||
|
||||
Image::Image(Image&& image)
|
||||
{
|
||||
this->m_image = image.m_image;
|
||||
this->m_colors = image.m_colors;
|
||||
this->m_width = image.m_width;
|
||||
this->m_height = image.m_height;
|
||||
this->m_channels = image.m_channels;
|
||||
image.m_image = nullptr;
|
||||
image.m_colors = nullptr;
|
||||
}
|
||||
|
||||
Image::Image(const std::vector<uint8>& imageBytes)
|
||||
{
|
||||
m_image = stbi_load_from_memory(imageBytes.data(), imageBytes.size(), &m_width, &m_height, &m_channels, STBI_rgb_alpha);
|
||||
if (m_image)
|
||||
stbi_uc* stbImage = stbi_load_from_memory(imageBytes.data(), imageBytes.size(), &m_width, &m_height, &m_channels, STBI_rgb_alpha);
|
||||
if (!stbImage)
|
||||
return;
|
||||
for (size_t i = 0; i < m_width * m_height * 4; i += 4)
|
||||
{
|
||||
for (int i = 0; i < m_width * m_height * 4; i += 4)
|
||||
{
|
||||
uint8 r = m_image[i];
|
||||
uint8 g = m_image[i + 1];
|
||||
uint8 b = m_image[i + 2];
|
||||
uint8 a = m_image[i + 3];
|
||||
m_image[i] = b;
|
||||
m_image[i + 1] = g;
|
||||
m_image[i + 2] = r;
|
||||
m_image[i + 3] = a;
|
||||
}
|
||||
uint8 r = stbImage[i];
|
||||
uint8 g = stbImage[i + 1];
|
||||
uint8 b = stbImage[i + 2];
|
||||
uint8 a = stbImage[i + 3];
|
||||
stbImage[i] = b;
|
||||
stbImage[i + 1] = g;
|
||||
stbImage[i + 2] = r;
|
||||
stbImage[i + 3] = a;
|
||||
}
|
||||
m_colors = reinterpret_cast<sint32*>(stbImage);
|
||||
}
|
||||
|
||||
bool Image::isOk() const
|
||||
{
|
||||
return m_image != nullptr;
|
||||
}
|
||||
|
||||
int* Image::intColors() const
|
||||
{
|
||||
return reinterpret_cast<int*>(m_image);
|
||||
return m_colors != nullptr;
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
{
|
||||
if (m_image)
|
||||
stbi_image_free(m_image);
|
||||
if (m_colors)
|
||||
stbi_image_free(m_colors);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
struct Image
|
||||
{
|
||||
uint8_t* m_image = nullptr;
|
||||
sint32* m_colors = nullptr;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
int m_channels = 0;
|
||||
@ -13,7 +13,5 @@ struct Image
|
||||
|
||||
bool isOk() const;
|
||||
|
||||
int* intColors() const;
|
||||
|
||||
~Image();
|
||||
};
|
||||
|
@ -1,10 +1,120 @@
|
||||
#include "JNIUtils.h"
|
||||
#include "Cafe/GameProfile/GameProfile.h"
|
||||
#include "GameTitleLoader.h"
|
||||
#include "AndroidGameTitleLoadedCallback.h"
|
||||
|
||||
namespace NativeGameTitles
|
||||
{
|
||||
GameTitleLoader s_gameTitleLoader;
|
||||
|
||||
std::list<fs::path> getCachesPaths(const TitleId& titleId)
|
||||
{
|
||||
std::list<fs::path> cachePaths{
|
||||
ActiveSettings::GetCachePath("shaderCache/driver/vk/{:016x}.bin", titleId),
|
||||
ActiveSettings::GetCachePath("shaderCache/precompiled/{:016x}_spirv.bin", titleId),
|
||||
ActiveSettings::GetCachePath("shaderCache/precompiled/{:016x}_gl.bin", titleId),
|
||||
ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_shaders.bin", titleId),
|
||||
ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_vkpipeline.bin", titleId),
|
||||
};
|
||||
|
||||
cachePaths.remove_if([](const fs::path& cachePath) {
|
||||
std::error_code ec;
|
||||
return !fs::exists(cachePath, ec);
|
||||
});
|
||||
|
||||
return cachePaths;
|
||||
}
|
||||
TitleId s_currentTitleId = 0;
|
||||
GameProfile s_currentGameProfile{};
|
||||
void LoadGameProfile(TitleId titleId)
|
||||
{
|
||||
if (s_currentTitleId == titleId)
|
||||
return;
|
||||
s_currentTitleId = titleId;
|
||||
s_currentGameProfile.Reset();
|
||||
s_currentGameProfile.Load(titleId);
|
||||
}
|
||||
} // namespace NativeGameTitles
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_isLoadingSharedLibrariesForTitleEnabled([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
return NativeGameTitles::s_currentGameProfile.ShouldLoadSharedLibraries().value_or(false);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setLoadingSharedLibrariesForTitleEnabled([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id, jboolean enabled)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
NativeGameTitles::s_currentGameProfile.SetShouldLoadSharedLibraries(enabled);
|
||||
NativeGameTitles::s_currentGameProfile.Save(game_title_id);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_getCpuModeForTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
return static_cast<jint>(NativeGameTitles::s_currentGameProfile.GetCPUMode().value_or(CPUMode::Auto));
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setCpuModeForTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id, jint cpu_mode)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
NativeGameTitles::s_currentGameProfile.SetCPUMode(static_cast<CPUMode>(cpu_mode));
|
||||
NativeGameTitles::s_currentGameProfile.Save(game_title_id);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_getThreadQuantumForTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
return NativeGameTitles::s_currentGameProfile.GetThreadQuantum();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setThreadQuantumForTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id, jint thread_quantum)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
NativeGameTitles::s_currentGameProfile.SetThreadQuantum(std::clamp(thread_quantum, 5000, 536870912));
|
||||
NativeGameTitles::s_currentGameProfile.Save(game_title_id);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_isShaderMultiplicationAccuracyForTitleEnabled([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
return NativeGameTitles::s_currentGameProfile.GetAccurateShaderMul() == AccurateShaderMulOption::True;
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setShaderMultiplicationAccuracyForTitleEnabled([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id, jboolean enabled)
|
||||
{
|
||||
NativeGameTitles::LoadGameProfile(game_title_id);
|
||||
NativeGameTitles::s_currentGameProfile.SetAccurateShaderMul(enabled ? AccurateShaderMulOption::True : AccurateShaderMulOption::False);
|
||||
NativeGameTitles::s_currentGameProfile.Save(game_title_id);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_titleHasShaderCacheFiles([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
return !NativeGameTitles::getCachesPaths(game_title_id).empty();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_removeShaderCacheFilesForTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id)
|
||||
{
|
||||
std::error_code ec;
|
||||
for (auto&& cacheFilePath : NativeGameTitles::getCachesPaths(game_title_id))
|
||||
fs::remove(cacheFilePath, ec);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setGameTitleFavorite([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong game_title_id, jboolean isFavorite)
|
||||
{
|
||||
GetConfig().SetGameListFavorite(game_title_id, isFavorite);
|
||||
g_config.Save();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
@ -16,7 +126,7 @@ Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_setGameTitleLoadedCallback(
|
||||
return;
|
||||
}
|
||||
jclass gameTitleLoadedCallbackClass = env->GetObjectClass(game_title_loaded_callback);
|
||||
jmethodID onGameTitleLoadedMID = env->GetMethodID(gameTitleLoadedCallbackClass, "onGameTitleLoaded", "(Ljava/lang/String;Ljava/lang/String;[III)V");
|
||||
jmethodID onGameTitleLoadedMID = env->GetMethodID(gameTitleLoadedCallbackClass, "onGameTitleLoaded", "(Linfo/cemu/Cemu/nativeinterface/NativeGameTitles$Game;)V");
|
||||
env->DeleteLocalRef(gameTitleLoadedCallbackClass);
|
||||
NativeGameTitles::s_gameTitleLoader.setOnTitleLoaded(std::make_shared<AndroidGameTitleLoadedCallback>(onGameTitleLoadedMID, game_title_loaded_callback));
|
||||
}
|
||||
@ -27,7 +137,7 @@ Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_reloadGameTitles([[maybe_un
|
||||
NativeGameTitles::s_gameTitleLoader.reloadGameTitles();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
|
||||
Java_info_cemu_Cemu_nativeinterface_NativeGameTitles_getInstalledGamesTitleIds(JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return JNIUtils::createJavaLongArrayList(env, CafeTitleList::GetAllTitleIds());
|
||||
|
@ -1,6 +0,0 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public record Game(String path, String title, Bitmap icon) {
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.opengl.Visibility;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -18,24 +20,34 @@ import java.util.stream.Collectors;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles.Game;
|
||||
|
||||
public class GameAdapter extends ListAdapter<Game, GameAdapter.ViewHolder> {
|
||||
private final GameTitleClickAction gameTitleClickAction;
|
||||
private List<Game> orignalGameList;
|
||||
private String filterText;
|
||||
private Game selectedGame;
|
||||
|
||||
public Game getSelectedGame() {
|
||||
return selectedGame;
|
||||
}
|
||||
|
||||
public static final DiffUtil.ItemCallback<Game> DIFF_CALLBACK = new DiffUtil.ItemCallback<>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Game oldItem, @NonNull Game newItem) {
|
||||
return oldItem.path().equals(newItem.path());
|
||||
return oldItem.titleId() == newItem.titleId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Game oldItem, @NonNull Game newItem) {
|
||||
return oldItem.path().equals(newItem.path());
|
||||
return oldItem.path().equals(newItem.path()) &&
|
||||
oldItem.titleId() == newItem.titleId() &&
|
||||
oldItem.isFavorite() == newItem.isFavorite();
|
||||
}
|
||||
};
|
||||
|
||||
public interface GameTitleClickAction {
|
||||
void action(String gamePath);
|
||||
void action(Game game);
|
||||
}
|
||||
|
||||
public GameAdapter(GameTitleClickAction gameTitleClickAction) {
|
||||
@ -50,7 +62,7 @@ public class GameAdapter extends ListAdapter<Game, GameAdapter.ViewHolder> {
|
||||
super.submitList(orignalGameList);
|
||||
return;
|
||||
}
|
||||
super.submitList(orignalGameList.stream().filter(g -> g.title().toLowerCase(Locale.US).contains(this.filterText)).collect(Collectors.toList()));
|
||||
super.submitList(orignalGameList.stream().filter(g -> g.name().toLowerCase(Locale.US).contains(this.filterText)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -63,14 +75,15 @@ public class GameAdapter extends ListAdapter<Game, GameAdapter.ViewHolder> {
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GameAdapter.ViewHolder holder, int position) {
|
||||
Game game = getItem(position);
|
||||
if (game != null) {
|
||||
holder.icon.setImageBitmap(game.icon());
|
||||
holder.text.setText(game.title());
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
String gamePath = game.path();
|
||||
gameTitleClickAction.action(gamePath);
|
||||
});
|
||||
}
|
||||
if (game == null) return;
|
||||
holder.icon.setImageBitmap(game.icon());
|
||||
holder.favoriteIcon.setVisibility(game.isFavorite() ? View.VISIBLE : View.GONE);
|
||||
holder.text.setText(game.name());
|
||||
holder.itemView.setOnClickListener(v -> gameTitleClickAction.action(game));
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
selectedGame = game;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void setFilterText(String filterText) {
|
||||
@ -82,17 +95,19 @@ public class GameAdapter extends ListAdapter<Game, GameAdapter.ViewHolder> {
|
||||
super.submitList(orignalGameList);
|
||||
return;
|
||||
}
|
||||
super.submitList(orignalGameList.stream().filter(g -> g.title().toLowerCase(Locale.US).contains(this.filterText)).collect(Collectors.toList()));
|
||||
super.submitList(orignalGameList.stream().filter(g -> g.name().toLowerCase(Locale.US).contains(this.filterText)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView icon;
|
||||
TextView text;
|
||||
ImageView favoriteIcon;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.game_icon);
|
||||
text = itemView.findViewById(R.id.game_title);
|
||||
favoriteIcon = itemView.findViewById(R.id.game_favorite_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
import info.cemu.Cemu.databinding.FragmentGameDetailsBinding;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles.Game;
|
||||
|
||||
public class GameDetailsFragment extends Fragment {
|
||||
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
FragmentGameDetailsBinding binding = FragmentGameDetailsBinding.inflate(inflater, container, false);
|
||||
var game = new ViewModelProvider(requireActivity()).get(GameViewModel.class).getGame();
|
||||
binding.gameTitleName.setText(game.name());
|
||||
binding.titleVersion.setText(String.valueOf(game.version()));
|
||||
if (game.icon() != null)
|
||||
binding.titleIcon.setImageBitmap(game.icon());
|
||||
if (game.dlc() != 0)
|
||||
binding.titleDlc.setText(String.valueOf(game.dlc()));
|
||||
binding.titleTimePlayed.setText(getTimePlayed(game));
|
||||
binding.titleLastPlayed.setText(getLastPlayedDate(game));
|
||||
binding.titleId.setText(String.valueOf(game.titleId()));
|
||||
binding.titleRegion.setText(getRegionName(game));
|
||||
NavigationUI.setupWithNavController(binding.gameDetailsToolbar, NavHostFragment.findNavController(this), new AppBarConfiguration.Builder().build());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private String getLastPlayedDate(Game game) {
|
||||
if (game.lastPlayedYear() == 0) return getString(R.string.never_played);
|
||||
LocalDate lastPlayedDate = LocalDate.of(game.lastPlayedYear(), game.lastPlayedMonth(), game.lastPlayedDay());
|
||||
return dateFormatter.format(lastPlayedDate);
|
||||
}
|
||||
|
||||
private String getTimePlayed(Game game) {
|
||||
if (game.minutesPlayed() == 0) return getString(R.string.never_played);
|
||||
if (game.minutesPlayed() < 60)
|
||||
return getString(R.string.minutes_played, game.minutesPlayed());
|
||||
return getString(R.string.hours_minutes_played, game.minutesPlayed() / 60, game.minutesPlayed() % 60);
|
||||
}
|
||||
|
||||
private @StringRes() int getRegionName(Game game) {
|
||||
return switch (game.region()) {
|
||||
case NativeGameTitles.CONSOLE_REGION_JPN -> R.string.console_region_japan;
|
||||
case NativeGameTitles.CONSOLE_REGION_USA -> R.string.console_region_usa;
|
||||
case NativeGameTitles.CONSOLE_REGION_EUR -> R.string.console_region_europe;
|
||||
case NativeGameTitles.CONSOLE_REGION_AUS_DEPR -> R.string.console_region_australia;
|
||||
case NativeGameTitles.CONSOLE_REGION_CHN -> R.string.console_region_china;
|
||||
case NativeGameTitles.CONSOLE_REGION_KOR -> R.string.console_region_korea;
|
||||
case NativeGameTitles.CONSOLE_REGION_TWN -> R.string.console_region_taiwan;
|
||||
case NativeGameTitles.CONSOLE_REGION_AUTO -> R.string.console_region_auto;
|
||||
default -> R.string.console_region_many;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles.Game;
|
||||
|
||||
public class GameListViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<Game>> gamesData;
|
||||
|
||||
private final TreeSet<Game> games = new TreeSet<>();
|
||||
|
||||
public LiveData<List<Game>> getGames() {
|
||||
return gamesData;
|
||||
}
|
||||
|
||||
public GameListViewModel() {
|
||||
this.gamesData = new MutableLiveData<>();
|
||||
NativeGameTitles.setGameTitleLoadedCallback(game -> {
|
||||
synchronized (GameListViewModel.this) {
|
||||
games.add(game);
|
||||
gamesData.postValue(new ArrayList<>(games));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setGameTitleFavorite(Game game, boolean isFavorite) {
|
||||
synchronized (this) {
|
||||
if (!games.contains(game)) return;
|
||||
NativeGameTitles.setGameTitleFavorite(game.titleId(), isFavorite);
|
||||
games.remove(game);
|
||||
Game newGame = new Game(
|
||||
game.titleId(),
|
||||
game.path(),
|
||||
game.name(),
|
||||
game.version(),
|
||||
game.dlc(),
|
||||
game.region(),
|
||||
game.lastPlayedYear(),
|
||||
game.lastPlayedMonth(),
|
||||
game.lastPlayedDay(),
|
||||
game.minutesPlayed(),
|
||||
isFavorite,
|
||||
game.icon()
|
||||
);
|
||||
games.add(newGame);
|
||||
gamesData.postValue(new ArrayList<>(games));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
NativeGameTitles.setGameTitleLoadedCallback(null);
|
||||
}
|
||||
|
||||
public void refreshGames() {
|
||||
games.clear();
|
||||
gamesData.setValue(null);
|
||||
NativeGameTitles.reloadGameTitles();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
import info.cemu.Cemu.databinding.FragmentGameProfileEditBinding;
|
||||
import info.cemu.Cemu.guibasecomponents.GenericRecyclerViewAdapter;
|
||||
import info.cemu.Cemu.guibasecomponents.HeaderRecyclerViewItem;
|
||||
import info.cemu.Cemu.guibasecomponents.SelectionAdapter;
|
||||
import info.cemu.Cemu.guibasecomponents.SingleSelectionRecyclerViewItem;
|
||||
import info.cemu.Cemu.guibasecomponents.ToggleRecyclerViewItem;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles;
|
||||
|
||||
public class GameProfileEditFragment extends Fragment {
|
||||
|
||||
private static @StringRes() int cpuModeToResourceNameId(int cpuMode) {
|
||||
return switch (cpuMode) {
|
||||
case NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER ->
|
||||
R.string.cpu_mode_single_core_interpreter;
|
||||
case NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER ->
|
||||
R.string.cpu_mode_single_core_recompiler;
|
||||
case NativeGameTitles.CPU_MODE_MULTICORERECOMPILER ->
|
||||
R.string.cpu_mode_multi_core_recompiler;
|
||||
default -> R.string.cpu_mode_auto;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
FragmentGameProfileEditBinding binding = FragmentGameProfileEditBinding.inflate(inflater, container, false);
|
||||
var game = new ViewModelProvider(requireActivity()).get(GameViewModel.class).getGame();
|
||||
long titleId = game.titleId();
|
||||
|
||||
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
|
||||
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new HeaderRecyclerViewItem(game.name()));
|
||||
|
||||
ToggleRecyclerViewItem loadSharedLibrariesToggle = new ToggleRecyclerViewItem("Load shared libraries",
|
||||
"Load libraries from the cafeLibs directory", NativeGameTitles.isLoadingSharedLibrariesForTitleEnabled(titleId),
|
||||
checked -> NativeGameTitles.setLoadingSharedLibrariesForTitleEnabled(titleId, checked));
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(loadSharedLibrariesToggle);
|
||||
|
||||
ToggleRecyclerViewItem shaderMultiplicationAccuracyToggle = new ToggleRecyclerViewItem("Shader multiplication accuracy",
|
||||
"Controls the accuracy of floating point multiplication in shaders", NativeGameTitles.isShaderMultiplicationAccuracyForTitleEnabled(titleId),
|
||||
checked -> NativeGameTitles.setShaderMultiplicationAccuracyForTitleEnabled(titleId, checked));
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(shaderMultiplicationAccuracyToggle);
|
||||
|
||||
int currentCpuMode = NativeGameTitles.getCpuModeForTitle(titleId);
|
||||
var cpuModeChoices = Stream.of(NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER,
|
||||
NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER,
|
||||
NativeGameTitles.CPU_MODE_MULTICORERECOMPILER,
|
||||
NativeGameTitles.CPU_MODE_AUTO)
|
||||
.map(cpuMode -> new SelectionAdapter.ChoiceItem<>(t -> t.setText(cpuModeToResourceNameId(cpuMode)), cpuMode))
|
||||
.collect(Collectors.toList());
|
||||
SelectionAdapter<Integer> cpuSelectionAdapter = new SelectionAdapter<>(cpuModeChoices, currentCpuMode);
|
||||
SingleSelectionRecyclerViewItem<Integer> cpuModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.cpu_mode),
|
||||
getString(cpuModeToResourceNameId(currentCpuMode)), cpuSelectionAdapter,
|
||||
(cpuMode, selectionRecyclerViewItem) -> {
|
||||
NativeGameTitles.setCpuModeForTitle(titleId, cpuMode);
|
||||
selectionRecyclerViewItem.setDescription(getString(cpuModeToResourceNameId(cpuMode)));
|
||||
});
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(cpuModeSelection);
|
||||
|
||||
int currentThreadQuantum = NativeGameTitles.getThreadQuantumForTitle(titleId);
|
||||
var threadQuantumChoices = Arrays.stream(NativeGameTitles.THREAD_QUANTUM_VALUES)
|
||||
.mapToObj(threadQuantum -> new SelectionAdapter.ChoiceItem<>(t -> t.setText(String.valueOf(threadQuantum)), threadQuantum))
|
||||
.collect(Collectors.toList());
|
||||
SelectionAdapter<Integer> threadQuantumSelectionAdapter = new SelectionAdapter<>(threadQuantumChoices, currentThreadQuantum);
|
||||
SingleSelectionRecyclerViewItem<Integer> threadQuantumSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.thread_quantum),
|
||||
String.valueOf(currentThreadQuantum), threadQuantumSelectionAdapter,
|
||||
(threadQuantum, selectionRecyclerViewItem) -> {
|
||||
NativeGameTitles.setThreadQuantumForTitle(titleId, threadQuantum);
|
||||
selectionRecyclerViewItem.setDescription(String.valueOf(threadQuantum));
|
||||
});
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(threadQuantumSelection);
|
||||
|
||||
binding.recyclerView.setAdapter(genericRecyclerViewAdapter);
|
||||
NavigationUI.setupWithNavController(binding.gameEditProfileToolbar, NavHostFragment.findNavController(this), new AppBarConfiguration.Builder().build());
|
||||
return binding.getRoot();
|
||||
}
|
||||
}
|
@ -1,47 +1,19 @@
|
||||
package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import static info.cemu.Cemu.nativeinterface.NativeGameTitles.*;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles;
|
||||
|
||||
public class GameViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<Game>> gamesData;
|
||||
private Game game = null;
|
||||
|
||||
private final ArrayList<Game> games = new ArrayList<>();
|
||||
|
||||
public LiveData<List<Game>> getGames() {
|
||||
return gamesData;
|
||||
public Game getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public GameViewModel() {
|
||||
this.gamesData = new MutableLiveData<>();
|
||||
NativeGameTitles.setGameTitleLoadedCallback((path, title, colors, width, height) -> {
|
||||
Bitmap icon = null;
|
||||
if (colors != null)
|
||||
icon = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
|
||||
Game game = new Game(path, title, icon);
|
||||
synchronized (GameViewModel.this) {
|
||||
games.add(game);
|
||||
gamesData.postValue(new ArrayList<>(games));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
NativeGameTitles.setGameTitleLoadedCallback(null);
|
||||
}
|
||||
|
||||
public void refreshGames() {
|
||||
games.clear();
|
||||
gamesData.setValue(null);
|
||||
NativeGameTitles.reloadGameTitles();
|
||||
public void setGame(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
}
|
||||
|
@ -2,48 +2,117 @@ package info.cemu.Cemu.gameview;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
import info.cemu.Cemu.databinding.FragmentGamesBinding;
|
||||
import info.cemu.Cemu.emulation.EmulationActivity;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles;
|
||||
import info.cemu.Cemu.nativeinterface.NativeGameTitles.Game;
|
||||
import info.cemu.Cemu.settings.SettingsActivity;
|
||||
import info.cemu.Cemu.settings.SettingsFragment;
|
||||
|
||||
public class GamesFragment extends Fragment {
|
||||
private GameAdapter gameAdapter;
|
||||
private GameListViewModel gameListViewModel;
|
||||
private GameViewModel gameViewModel;
|
||||
private boolean refreshing = false;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
gameAdapter = new GameAdapter(titleId -> {
|
||||
gameAdapter = new GameAdapter(game -> {
|
||||
Intent intent = new Intent(getContext(), EmulationActivity.class);
|
||||
intent.putExtra(EmulationActivity.LAUNCH_PATH, titleId);
|
||||
intent.putExtra(EmulationActivity.LAUNCH_PATH, game.path());
|
||||
startActivity(intent);
|
||||
});
|
||||
gameViewModel = new ViewModelProvider(this).get(GameViewModel.class);
|
||||
gameViewModel.getGames().observe(this, gameList -> gameAdapter.submitList(gameList));
|
||||
gameListViewModel = new ViewModelProvider(this).get(GameListViewModel.class);
|
||||
gameViewModel = new ViewModelProvider(requireActivity()).get(GameViewModel.class);
|
||||
gameListViewModel.getGames().observe(this, gameList -> gameAdapter.submitList(gameList));
|
||||
NativeGameTitles.reloadGameTitles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
gameViewModel.refreshGames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v, @Nullable ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
MenuInflater inflater = requireActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_game, menu);
|
||||
Game selectedGame = gameAdapter.getSelectedGame();
|
||||
menu.findItem(R.id.favorite).setChecked(selectedGame.isFavorite());
|
||||
menu.findItem(R.id.remove_shader_caches).setEnabled(NativeGameTitles.titleHasShaderCacheFiles(selectedGame.titleId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(@NonNull MenuItem item) {
|
||||
Game game = gameAdapter.getSelectedGame();
|
||||
if (game == null) {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.favorite) {
|
||||
gameListViewModel.setGameTitleFavorite(game, !game.isFavorite());
|
||||
return true;
|
||||
}
|
||||
if (itemId == R.id.game_profile) {
|
||||
gameViewModel.setGame(game);
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_games_fragment_to_game_edit_profile);
|
||||
return true;
|
||||
}
|
||||
if (itemId == R.id.remove_shader_caches) {
|
||||
removeShaderCachesForGame(game);
|
||||
return true;
|
||||
}
|
||||
if (itemId == R.id.about_title) {
|
||||
gameViewModel.setGame(game);
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_games_fragment_to_game_details_fragment);
|
||||
return true;
|
||||
}
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
private void removeShaderCachesForGame(Game game) {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
|
||||
builder.setTitle(R.string.remove_shader_caches)
|
||||
.setMessage(getString(R.string.remove_shader_caches_message, game.name()))
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
NativeGameTitles.removeShaderCacheFilesForTitle(game.titleId());
|
||||
Toast.makeText(requireContext(), R.string.shader_caches_removed_notification, Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
FragmentGamesBinding binding = FragmentGamesBinding.inflate(inflater, container, false);
|
||||
RecyclerView recyclerView = binding.gamesRecyclerView;
|
||||
registerForContextMenu(recyclerView);
|
||||
binding.settingsButton.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(requireActivity(), SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
@ -51,8 +120,13 @@ public class GamesFragment extends Fragment {
|
||||
View rootView = binding.getRoot();
|
||||
|
||||
binding.gamesSwipeRefresh.setOnRefreshListener(() -> {
|
||||
gameViewModel.refreshGames();
|
||||
binding.gamesSwipeRefresh.setRefreshing(false);
|
||||
if (refreshing) return;
|
||||
refreshing = true;
|
||||
handler.postDelayed(() -> {
|
||||
binding.gamesSwipeRefresh.setRefreshing(false);
|
||||
refreshing = false;
|
||||
}, 1000);
|
||||
gameListViewModel.refreshGames();
|
||||
});
|
||||
recyclerView.setAdapter(gameAdapter);
|
||||
binding.searchText.addTextChangedListener(new TextWatcher() {
|
||||
|
@ -7,6 +7,8 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
|
||||
public class HeaderRecyclerViewItem implements RecyclerViewItem {
|
||||
@ -20,10 +22,17 @@ public class HeaderRecyclerViewItem implements RecyclerViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
private final int headerResourceIdText;
|
||||
private final Optional<Integer> headerResourceIdText;
|
||||
private final String headerText;
|
||||
|
||||
public HeaderRecyclerViewItem(int headerResourceIdText) {
|
||||
this.headerResourceIdText = headerResourceIdText;
|
||||
this.headerResourceIdText = Optional.of(headerResourceIdText);
|
||||
this.headerText = null;
|
||||
}
|
||||
|
||||
public HeaderRecyclerViewItem(String headerText) {
|
||||
this.headerResourceIdText = Optional.empty();
|
||||
this.headerText = headerText;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,6 +43,10 @@ public class HeaderRecyclerViewItem implements RecyclerViewItem {
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) viewHolder;
|
||||
headerViewHolder.header.setText(headerResourceIdText);
|
||||
if (headerResourceIdText.isEmpty()) {
|
||||
headerViewHolder.header.setText(headerText);
|
||||
return;
|
||||
}
|
||||
headerViewHolder.header.setText(headerResourceIdText.get());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package info.cemu.Cemu.guibasecomponents;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
|
||||
import info.cemu.Cemu.R;
|
||||
|
||||
public class ToggleRecyclerViewItem implements RecyclerViewItem {
|
||||
public interface OnCheckedChangeListener {
|
||||
void onCheckChanged(boolean checked);
|
||||
}
|
||||
|
||||
private static class ToggleViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView label;
|
||||
TextView description;
|
||||
MaterialSwitch toggle;
|
||||
|
||||
public ToggleViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
label = itemView.findViewById(R.id.toggle_label);
|
||||
description = itemView.findViewById(R.id.toggle_description);
|
||||
toggle = itemView.findViewById(R.id.toggle);
|
||||
}
|
||||
}
|
||||
|
||||
private final String label;
|
||||
private final String description;
|
||||
private boolean checked;
|
||||
private final OnCheckedChangeListener onCheckedChangeListener;
|
||||
|
||||
public ToggleRecyclerViewItem(String label, String description, boolean checked, OnCheckedChangeListener onCheckedChangeListener) {
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
this.checked = checked;
|
||||
this.onCheckedChangeListener = onCheckedChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new ToggleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_toggle, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
|
||||
ToggleViewHolder toggleViewHolder = (ToggleViewHolder) viewHolder;
|
||||
toggleViewHolder.label.setText(label);
|
||||
toggleViewHolder.description.setText(description);
|
||||
toggleViewHolder.toggle.setChecked(checked);
|
||||
toggleViewHolder.itemView.setOnClickListener(view -> {
|
||||
checked = !toggleViewHolder.toggle.isChecked();
|
||||
toggleViewHolder.toggle.setChecked(checked);
|
||||
if (onCheckedChangeListener != null) onCheckedChangeListener.onCheckChanged(checked);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package info.cemu.Cemu.inputoverlay;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.VibrationEffect;
|
||||
@ -41,18 +40,19 @@ public class InputOverlaySurfaceView extends SurfaceView implements View.OnTouch
|
||||
}
|
||||
|
||||
public void setInputMode(InputMode inputMode) {
|
||||
if (inputs != null) {
|
||||
if (this.inputMode != InputMode.DEFAULT) {
|
||||
for (var input : inputs) {
|
||||
input.saveConfiguration();
|
||||
}
|
||||
} else {
|
||||
for (var input : inputs) {
|
||||
input.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.inputMode = inputMode;
|
||||
if (inputs == null) {
|
||||
return;
|
||||
}
|
||||
if (this.inputMode != InputMode.DEFAULT) {
|
||||
for (var input : inputs) {
|
||||
input.saveConfiguration();
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (var input : inputs) {
|
||||
input.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public InputMode getInputMode() {
|
||||
@ -267,10 +267,21 @@ public class InputOverlaySurfaceView extends SurfaceView implements View.OnTouch
|
||||
}
|
||||
|
||||
void onJoystickStateChange(OverlayJoystick joystick, float x, float y) {
|
||||
float up = y < 0 ? -y : 0;
|
||||
float down = y > 0 ? y : 0;
|
||||
float left = x < 0 ? -x : 0;
|
||||
float right = x > 0 ? x : 0;
|
||||
float up, down, left, right;
|
||||
if (y < 0) {
|
||||
up = -y;
|
||||
down = 0;
|
||||
} else {
|
||||
up = 0;
|
||||
down = y;
|
||||
}
|
||||
if (x < 0) {
|
||||
left = -x;
|
||||
right = 0;
|
||||
} else {
|
||||
left = 0;
|
||||
right = x;
|
||||
}
|
||||
switch (nativeControllerType) {
|
||||
case NativeInput.EMULATED_CONTROLLER_TYPE_VPAD ->
|
||||
onVPADJoystickStateChange(joystick, up, down, left, right);
|
||||
|
@ -1,12 +1,87 @@
|
||||
package info.cemu.Cemu.nativeinterface;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class NativeGameTitles {
|
||||
public interface GameTitleLoadedCallback {
|
||||
void onGameTitleLoaded(String path, String title, int[] colors, int width, int height);
|
||||
public static final int CONSOLE_REGION_JPN = 0x1;
|
||||
public static final int CONSOLE_REGION_USA = 0x2;
|
||||
public static final int CONSOLE_REGION_EUR = 0x4;
|
||||
public static final int CONSOLE_REGION_AUS_DEPR = 0x8;
|
||||
public static final int CONSOLE_REGION_CHN = 0x10;
|
||||
public static final int CONSOLE_REGION_KOR = 0x20;
|
||||
public static final int CONSOLE_REGION_TWN = 0x40;
|
||||
public static final int CONSOLE_REGION_AUTO = 0xFF;
|
||||
|
||||
public record Game(
|
||||
long titleId,
|
||||
String path,
|
||||
String name,
|
||||
short version,
|
||||
short dlc,
|
||||
int region,
|
||||
short lastPlayedYear,
|
||||
short lastPlayedMonth,
|
||||
short lastPlayedDay,
|
||||
int minutesPlayed,
|
||||
boolean isFavorite,
|
||||
Bitmap icon
|
||||
) implements Comparable<Game> {
|
||||
@Override
|
||||
public int compareTo(Game other) {
|
||||
if (titleId == other.titleId) {
|
||||
return 0;
|
||||
}
|
||||
if (isFavorite && !other.isFavorite) {
|
||||
return -1;
|
||||
}
|
||||
if (!isFavorite && other.isFavorite) {
|
||||
return 1;
|
||||
}
|
||||
return name.compareTo(other.name);
|
||||
}
|
||||
}
|
||||
|
||||
public interface GameTitleLoadedCallback {
|
||||
void onGameTitleLoaded(Game game);
|
||||
}
|
||||
|
||||
public static native boolean isLoadingSharedLibrariesForTitleEnabled(long gameTitleId);
|
||||
|
||||
public static native void setLoadingSharedLibrariesForTitleEnabled(long gameTitleId, boolean enabled);
|
||||
|
||||
public final static int CPU_MODE_SINGLECOREINTERPRETER = 0;
|
||||
public final static int CPU_MODE_SINGLECORERECOMPILER = 1;
|
||||
public final static int CPU_MODE_MULTICORERECOMPILER = 3;
|
||||
public final static int CPU_MODE_AUTO = 4;
|
||||
|
||||
public static native int getCpuModeForTitle(long gameTitleId);
|
||||
|
||||
public static native void setCpuModeForTitle(long gameTitleId, int cpuMode);
|
||||
|
||||
public static final int[] THREAD_QUANTUM_VALUES = new int[]{
|
||||
20000,
|
||||
45000,
|
||||
60000,
|
||||
80000,
|
||||
100000,
|
||||
};
|
||||
|
||||
public static native int getThreadQuantumForTitle(long gameTitleId);
|
||||
|
||||
public static native void setThreadQuantumForTitle(long gameTitleId, int threadQuantum);
|
||||
|
||||
public static native boolean isShaderMultiplicationAccuracyForTitleEnabled(long gameTitleId);
|
||||
|
||||
public static native void setShaderMultiplicationAccuracyForTitleEnabled(long gameTitleId, boolean enabled);
|
||||
|
||||
public static native boolean titleHasShaderCacheFiles(long gameTitleId);
|
||||
|
||||
public static native void removeShaderCacheFilesForTitle(long gameTitleId);
|
||||
|
||||
public static native void setGameTitleFavorite(long gameTitleId, boolean isFavorite);
|
||||
|
||||
public static native void setGameTitleLoadedCallback(GameTitleLoadedCallback gameTitleLoadedCallback);
|
||||
|
||||
public static native void reloadGameTitles();
|
||||
|
10
src/android/app/src/main/res/drawable/ic_favorite.xml
Normal file
10
src/android/app/src/main/res/drawable/ic_favorite.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M320,720L480,598L640,720L580,522L740,408L544,408L480,200L416,408L220,408L380,522L320,720ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
@ -13,5 +13,5 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
app:navGraph="@navigation/nav_main_graph" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
148
src/android/app/src/main/res/layout/fragment_game_details.xml
Normal file
148
src/android/app/src/main/res/layout/fragment_game_details.xml
Normal file
@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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:orientation="vertical"
|
||||
tools:context=".gameview.GameDetailsFragment">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/game_details_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="8dp"
|
||||
app:cardCornerRadius="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/title_icon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:contentDescription="@string/game_icon"
|
||||
android:scaleType="fitXY"
|
||||
tools:ignore="ImageContrastCheck" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_name"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/game_title_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_id"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/version"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/dlc"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_dlc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_time_played"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_time_played"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_last_played"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_last_played"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_region"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_region"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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:orientation="vertical"
|
||||
tools:context=".gameview.GameProfileEditFragment">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/game_edit_profile_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="8dp"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
</LinearLayout>
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
@ -7,12 +8,37 @@
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/game_icon"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:contentDescription="@string/game_icon"
|
||||
android:scaleType="fitXY" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/game_icon"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:contentDescription="@string/game_icon"
|
||||
android:scaleType="fitXY" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/game_favorite_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:contentDescription="@string/game_favorite_description"
|
||||
android:src="@drawable/ic_favorite"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?android:attr/colorPrimary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/game_title"
|
||||
|
47
src/android/app/src/main/res/layout/layout_toggle.xml
Normal file
47
src/android/app/src/main/res/layout/layout_toggle.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toggle_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_toStartOf="@+id/toggle"
|
||||
android:text="Toggle label"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toggle_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/toggle_label"
|
||||
android:layout_alignStart="@+id/toggle_label"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_toStartOf="@+id/toggle"
|
||||
android:text="Toggle description"
|
||||
android:textAlignment="textStart"
|
||||
android:textAppearance="?attr/textAppearanceSubtitle1"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/toggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false" />
|
||||
|
||||
</RelativeLayout>
|
16
src/android/app/src/main/res/menu/menu_game.xml
Normal file
16
src/android/app/src/main/res/menu/menu_game.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/favorite"
|
||||
android:checkable="true"
|
||||
android:title="Favorite" />
|
||||
<item
|
||||
android:id="@+id/game_profile"
|
||||
android:title="Edit game profile" />
|
||||
<item
|
||||
android:id="@+id/remove_shader_caches"
|
||||
android:title="Remove shader caches" />
|
||||
<item
|
||||
android:id="@+id/about_title"
|
||||
android:title="About title" />
|
||||
</menu>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation 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:id="@+id/nav_graph"
|
||||
app:startDestination="@id/gamesFragment">
|
||||
<fragment
|
||||
android:id="@+id/gamesFragment"
|
||||
android:name="info.cemu.Cemu.gameview.GamesFragment"
|
||||
android:label="GamesFragment" />
|
||||
</navigation>
|
28
src/android/app/src/main/res/navigation/nav_main_graph.xml
Normal file
28
src/android/app/src/main/res/navigation/nav_main_graph.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation 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:id="@+id/nav_graph"
|
||||
app:startDestination="@id/games_fragment">
|
||||
<fragment
|
||||
android:id="@+id/games_fragment"
|
||||
android:name="info.cemu.Cemu.gameview.GamesFragment"
|
||||
tools:layout="@layout/fragment_games">
|
||||
<action
|
||||
android:id="@+id/action_games_fragment_to_game_details_fragment"
|
||||
app:destination="@id/game_details_fragment" />
|
||||
<action
|
||||
android:id="@+id/action_games_fragment_to_game_edit_profile"
|
||||
app:destination="@id/game_edit_profile" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/game_details_fragment"
|
||||
android:name="info.cemu.Cemu.gameview.GameDetailsFragment"
|
||||
android:label="@string/about_title"
|
||||
tools:layout="@layout/fragment_game_details" />
|
||||
<fragment
|
||||
android:id="@+id/game_edit_profile"
|
||||
android:name="info.cemu.Cemu.gameview.GameProfileEditFragment"
|
||||
android:label="@string/edit_game_profile"
|
||||
tools:layout="@layout/fragment_game_profile_edit" />
|
||||
</navigation>
|
@ -191,4 +191,35 @@
|
||||
<string name="notifications_text_scale">Notifications text scale</string>
|
||||
<string name="exit">Exit</string>
|
||||
<string name="show_input_overlay">Show input overlay</string>
|
||||
<string name="game_favorite_description">Game is marked as favorite</string>
|
||||
<string name="remove_shader_caches">Remove shader caches</string>
|
||||
<string name="remove_shader_caches_message">Remove the shader caches for %1$s?</string>
|
||||
<string name="minutes_played">Minutes: %d</string>
|
||||
<string name="hours_minutes_played">Hours: %1$d Minutes: %2$d</string>
|
||||
<string name="never_played">Never played</string>
|
||||
<string name="console_region_japan">Japan</string>
|
||||
<string name="console_region_usa">USA</string>
|
||||
<string name="console_region_europe">Europe</string>
|
||||
<string name="console_region_australia">Australia</string>
|
||||
<string name="console_region_china">China</string>
|
||||
<string name="console_region_korea">Korea</string>
|
||||
<string name="console_region_taiwan">Taiwan</string>
|
||||
<string name="console_region_auto">Auto</string>
|
||||
<string name="console_region_many">Many</string>
|
||||
<string name="shader_caches_removed_notification">Shader caches removed</string>
|
||||
<string name="about_title">About title</string>
|
||||
<string name="title_name">Title name</string>
|
||||
<string name="title_id">Title ID</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="dlc">DLC</string>
|
||||
<string name="title_time_played">You\'ve played</string>
|
||||
<string name="title_last_played">Last played</string>
|
||||
<string name="title_region">Region</string>
|
||||
<string name="edit_game_profile">Edit game profile</string>
|
||||
<string name="cpu_mode_auto">Auto (recommended)</string>
|
||||
<string name="cpu_mode_single_core_interpreter">Single-core interpreter</string>
|
||||
<string name="cpu_mode_single_core_recompiler">Single-core recompiler</string>
|
||||
<string name="cpu_mode_multi_core_recompiler">Multi-core recompiler</string>
|
||||
<string name="cpu_mode">CPU mode</string>
|
||||
<string name="thread_quantum">Thread quantum</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user