Compare commits

..
Author SHA1 Message Date
YigaUpdate 51bcf4a724 Update Qt version
I propose this update of qt.
2023-11-28 16:03:31 +01:00
yuzubot 9d0418d7a9 "Merge Tagged PR 12154" 2023-11-28 13:03:11 +00:00
yuzubot 3b408d26c1 "Merge Tagged PR 12173" 2023-11-28 13:03:10 +00:00
yuzubot c4d5075c64 "Merge Tagged PR 12203" 2023-11-28 13:03:08 +00:00
450 changed files with 27681 additions and 71004 deletions

No files matched your search

+1 -1
View File
@@ -3,4 +3,4 @@
[codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
ignore-words-list = aci,allright,ba,canonicalizations,deques,fpr,froms,hda,inout,lod,masia,nam,nax,nce,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink
ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink
-20
View File
@@ -68,26 +68,6 @@ jobs:
with:
name: ${{ matrix.type }}
path: artifacts/
build-mac:
name: 'test build (macos)'
needs: format
runs-on: macos-13
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Install dependencies
run: |
# workaround for https://github.com/actions/setup-python/issues/577
brew install autoconf automake [email protected] ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite [email protected]
- name: Build
run: |
mkdir build
cd build
export Qt5_DIR="/usr/local/opt/qt@5/lib/cmake"
cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF
ninja
build-msvc:
name: 'test build (windows, msvc)'
needs: format
-3
View File
@@ -61,6 +61,3 @@
[submodule "simpleini"]
path = externals/simpleini
url = https://github.com/brofield/simpleini.git
[submodule "oaknut"]
path = externals/oaknut
url = https://github.com/merryhime/oaknut
-4
View File
@@ -151,7 +151,3 @@ License: GPL-3.0-or-later
Files: externals/stb/*
Copyright: Sean Barrett
License: MIT
Files: externals/gamemode/*
Copyright: Copyright 2017-2019 Feral Interactive
License: BSD-3-Clause
+3 -15
View File
@@ -142,9 +142,6 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (ENABLE_WEB_SERVICE)
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
endif()
if (ANDROID)
list(APPEND VCPKG_MANIFEST_FEATURES "android")
endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@@ -263,11 +260,6 @@ if (UNIX)
add_definitions(-DYUZU_UNIX=1)
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
set(HAS_NCE 1)
add_definitions(-DHAS_NCE=1)
endif()
# Configure C++ standard
# ===========================
@@ -305,7 +297,7 @@ find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED)
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
find_package(Vulkan 1.3.274 REQUIRED)
find_package(Vulkan 1.3.256 REQUIRED)
endif()
if (ENABLE_LIBUSB)
@@ -346,10 +338,6 @@ if(ENABLE_OPENSSL)
find_package(OpenSSL 1.1.1 REQUIRED)
endif()
if (UNIX AND NOT APPLE)
find_package(gamemode 1.7 MODULE)
endif()
# Please consider this as a stub
if(ENABLE_QT6 AND Qt6_LOCATION)
list(APPEND CMAKE_PREFIX_PATH "${Qt6_LOCATION}")
@@ -378,10 +366,10 @@ endfunction(set_yuzu_qt_components)
# Qt5 requires that we find components, so it doesn't fit our pretty little find package function
if(ENABLE_QT)
set(QT_VERSION 5.15)
set(QT_VERSION 5.15.16)
# These are used to specify minimum versions
set(QT5_VERSION 5.15)
set(QT6_VERSION 6.3.1)
set(QT6_VERSION 6.5.3)
set_yuzu_qt_components()
if (ENABLE_QT6)
-15
View File
@@ -1,15 +0,0 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
find_package(PkgConfig QUIET)
pkg_search_module(GAMEMODE QUIET IMPORTED_TARGET gamemode)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(gamemode
REQUIRED_VARS GAMEMODE_INCLUDEDIR
VERSION_VAR GAMEMODE_VERSION
)
if (gamemode_FOUND AND NOT TARGET gamemode::headers)
add_library(gamemode::headers ALIAS PkgConfig::GAMEMODE)
endif()
-19
View File
@@ -1,19 +0,0 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Allow systemd-logind to manage user access to hidraw with this file
# On most systems, this file should be installed to /etc/udev/rules.d/72-yuzu-input.rules
# Consult your distro if this is not the case
# Switch Pro Controller (USB/Bluetooth)
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", MODE="0660", TAG+="uaccess"
KERNEL=="hidraw*", KERNELS=="*057e:2009*", MODE="0660", TAG+="uaccess"
# Joy-Con L (Bluetooth)
KERNEL=="hidraw*", KERNELS=="*057e:2006*", MODE="0660", TAG+="uaccess"
# Joy-Con R (Bluetooth)
KERNEL=="hidraw*", KERNELS=="*057e:2007*", MODE="0660", TAG+="uaccess"
# Joy-Con Charging Grip (USB)
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="200e", MODE="0660", TAG+="uaccess"
+870 -1951
View File
File diff suppressed because it is too large. Load diff
+860 -1950
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+765 -1851
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+756 -1840
View File
File diff suppressed because it is too large. Load diff
+755 -1842
View File
File diff suppressed because it is too large. Load diff
+825 -1914
View File
File diff suppressed because it is too large. Load diff
+751 -1832
View File
File diff suppressed because it is too large. Load diff
+778 -1863
View File
File diff suppressed because it is too large. Load diff
+833 -1917
View File
File diff suppressed because it is too large. Load diff
+752 -1836
View File
File diff suppressed because it is too large. Load diff
+753 -1837
View File
File diff suppressed because it is too large. Load diff
+753 -1837
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+754 -1835
View File
File diff suppressed because it is too large. Load diff
+754 -1835
View File
File diff suppressed because it is too large. Load diff
+752 -1836
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+752 -1833
View File
File diff suppressed because it is too large. Load diff
+752 -1836
View File
File diff suppressed because it is too large. Load diff
+752 -1836
View File
File diff suppressed because it is too large. Load diff
+752 -1836
View File
File diff suppressed because it is too large. Load diff
+754 -1840
View File
File diff suppressed because it is too large. Load diff
+886 -1972
View File
File diff suppressed because it is too large. Load diff
-10
View File
@@ -20,10 +20,6 @@ if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
endif()
# Dynarmic
if (ARCHITECTURE_arm64 AND NOT TARGET merry::oaknut)
add_subdirectory(oaknut)
endif()
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
set(DYNARMIC_IGNORE_ASSERTS ON)
add_subdirectory(dynarmic)
@@ -193,12 +189,6 @@ if (ANDROID)
endif()
endif()
if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers)
add_library(gamemode INTERFACE)
target_include_directories(gamemode INTERFACE gamemode)
add_library(gamemode::headers ALIAS gamemode)
endif()
# Breakpad
# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
-376
View File
@@ -1,376 +0,0 @@
/*
Copyright (c) 2017-2019, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CLIENT_GAMEMODE_H
#define CLIENT_GAMEMODE_H
/*
* GameMode supports the following client functions
* Requests are refcounted in the daemon
*
* int gamemode_request_start() - Request gamemode starts
* 0 if the request was sent successfully
* -1 if the request failed
*
* int gamemode_request_end() - Request gamemode ends
* 0 if the request was sent successfully
* -1 if the request failed
*
* GAMEMODE_AUTO can be defined to make the above two functions apply during static init and
* destruction, as appropriate. In this configuration, errors will be printed to stderr
*
* int gamemode_query_status() - Query the current status of gamemode
* 0 if gamemode is inactive
* 1 if gamemode is active
* 2 if gamemode is active and this client is registered
* -1 if the query failed
*
* int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process
* 0 if the request was sent successfully
* -1 if the request failed
* -2 if the request was rejected
*
* int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process
* 0 if the request was sent successfully
* -1 if the request failed
* -2 if the request was rejected
*
* int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process
* 0 if gamemode is inactive
* 1 if gamemode is active
* 2 if gamemode is active and this client is registered
* -1 if the query failed
*
* const char* gamemode_error_string() - Get an error string
* returns a string describing any of the above errors
*
* Note: All the above requests can be blocking - dbus requests can and will block while the daemon
* handles the request. It is not recommended to make these calls in performance critical code
*/
#include <stdbool.h>
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
static char internal_gamemode_client_error_string[512] = { 0 };
/**
* Load libgamemode dynamically to dislodge us from most dependencies.
* This allows clients to link and/or use this regardless of runtime.
* See SDL2 for an example of the reasoning behind this in terms of
* dynamic versioning as well.
*/
static volatile int internal_libgamemode_loaded = 1;
/* Typedefs for the functions to load */
typedef int (*api_call_return_int)(void);
typedef const char *(*api_call_return_cstring)(void);
typedef int (*api_call_pid_return_int)(pid_t);
/* Storage for functors */
static api_call_return_int REAL_internal_gamemode_request_start = NULL;
static api_call_return_int REAL_internal_gamemode_request_end = NULL;
static api_call_return_int REAL_internal_gamemode_query_status = NULL;
static api_call_return_cstring REAL_internal_gamemode_error_string = NULL;
static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL;
static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL;
static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL;
/**
* Internal helper to perform the symbol binding safely.
*
* Returns 0 on success and -1 on failure
*/
__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(
void *handle, const char *name, void **out_func, size_t func_size, bool required)
{
void *symbol_lookup = NULL;
char *dl_error = NULL;
/* Safely look up the symbol */
symbol_lookup = dlsym(handle, name);
dl_error = dlerror();
if (required && (dl_error || !symbol_lookup)) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"dlsym failed - %s",
dl_error);
return -1;
}
/* Have the symbol correctly, copy it to make it usable */
memcpy(out_func, &symbol_lookup, func_size);
return 0;
}
/**
* Loads libgamemode and needed functions
*
* Returns 0 on success and -1 on failure
*/
__attribute__((always_inline)) static inline int internal_load_libgamemode(void)
{
/* We start at 1, 0 is a success and -1 is a fail */
if (internal_libgamemode_loaded != 1) {
return internal_libgamemode_loaded;
}
/* Anonymous struct type to define our bindings */
struct binding {
const char *name;
void **functor;
size_t func_size;
bool required;
} bindings[] = {
{ "real_gamemode_request_start",
(void **)&REAL_internal_gamemode_request_start,
sizeof(REAL_internal_gamemode_request_start),
true },
{ "real_gamemode_request_end",
(void **)&REAL_internal_gamemode_request_end,
sizeof(REAL_internal_gamemode_request_end),
true },
{ "real_gamemode_query_status",
(void **)&REAL_internal_gamemode_query_status,
sizeof(REAL_internal_gamemode_query_status),
false },
{ "real_gamemode_error_string",
(void **)&REAL_internal_gamemode_error_string,
sizeof(REAL_internal_gamemode_error_string),
true },
{ "real_gamemode_request_start_for",
(void **)&REAL_internal_gamemode_request_start_for,
sizeof(REAL_internal_gamemode_request_start_for),
false },
{ "real_gamemode_request_end_for",
(void **)&REAL_internal_gamemode_request_end_for,
sizeof(REAL_internal_gamemode_request_end_for),
false },
{ "real_gamemode_query_status_for",
(void **)&REAL_internal_gamemode_query_status_for,
sizeof(REAL_internal_gamemode_query_status_for),
false },
};
void *libgamemode = NULL;
/* Try and load libgamemode */
libgamemode = dlopen("libgamemode.so.0", RTLD_NOW);
if (!libgamemode) {
/* Attempt to load unversioned library for compatibility with older
* versions (as of writing, there are no ABI changes between the two -
* this may need to change if ever ABI-breaking changes are made) */
libgamemode = dlopen("libgamemode.so", RTLD_NOW);
if (!libgamemode) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"dlopen failed - %s",
dlerror());
internal_libgamemode_loaded = -1;
return -1;
}
}
/* Attempt to bind all symbols */
for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) {
struct binding *binder = &bindings[i];
if (internal_bind_libgamemode_symbol(libgamemode,
binder->name,
binder->functor,
binder->func_size,
binder->required)) {
internal_libgamemode_loaded = -1;
return -1;
};
}
/* Success */
internal_libgamemode_loaded = 0;
return 0;
}
/**
* Redirect to the real libgamemode
*/
__attribute__((always_inline)) static inline const char *gamemode_error_string(void)
{
/* If we fail to load the system gamemode, or we have an error string already, return our error
* string instead of diverting to the system version */
if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') {
return internal_gamemode_client_error_string;
}
/* Assert for static analyser that the function is not NULL */
assert(REAL_internal_gamemode_error_string != NULL);
return REAL_internal_gamemode_error_string();
}
/**
* Redirect to the real libgamemode
* Allow automatically requesting game mode
* Also prints errors as they happen.
*/
#ifdef GAMEMODE_AUTO
__attribute__((constructor))
#else
__attribute__((always_inline)) static inline
#endif
int gamemode_request_start(void)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
#ifdef GAMEMODE_AUTO
fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
#endif
return -1;
}
/* Assert for static analyser that the function is not NULL */
assert(REAL_internal_gamemode_request_start != NULL);
if (REAL_internal_gamemode_request_start() < 0) {
#ifdef GAMEMODE_AUTO
fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
#endif
return -1;
}
return 0;
}
/* Redirect to the real libgamemode */
#ifdef GAMEMODE_AUTO
__attribute__((destructor))
#else
__attribute__((always_inline)) static inline
#endif
int gamemode_request_end(void)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
#ifdef GAMEMODE_AUTO
fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
#endif
return -1;
}
/* Assert for static analyser that the function is not NULL */
assert(REAL_internal_gamemode_request_end != NULL);
if (REAL_internal_gamemode_request_end() < 0) {
#ifdef GAMEMODE_AUTO
fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string());
#endif
return -1;
}
return 0;
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_query_status(void)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_query_status == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_query_status missing (older host?)");
return -1;
}
return REAL_internal_gamemode_query_status();
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_request_start_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_request_start_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_request_start_for(pid);
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_request_end_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_request_end_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_request_end_for(pid);
}
/* Redirect to the real libgamemode */
__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid)
{
/* Need to load gamemode */
if (internal_load_libgamemode() < 0) {
return -1;
}
if (REAL_internal_gamemode_query_status_for == NULL) {
snprintf(internal_gamemode_client_error_string,
sizeof(internal_gamemode_client_error_string),
"gamemode_query_status_for missing (older host?)");
return -1;
}
return REAL_internal_gamemode_query_status_for(pid);
}
#endif // CLIENT_GAMEMODE_H
-1
Submodule externals/oaknut deleted from 918bd94f02.
+1 -1
+1 -2
View File
@@ -174,8 +174,7 @@ android {
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_BUNDLED_VCPKG=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
"-DYUZU_ENABLE_LTO=ON",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
"-DYUZU_ENABLE_LTO=ON"
)
abiFilters("arm64-v8a", "x86_64")
@@ -230,6 +230,8 @@ object NativeLibrary {
*/
external fun onTouchReleased(finger_id: Int)
external fun initGameIni(gameID: String?)
external fun setAppDirectory(directory: String)
/**
@@ -239,8 +241,6 @@ object NativeLibrary {
*/
external fun installFileToNand(filename: String, extension: String): Int
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
external fun initializeGpuDriver(
hookLibDir: String?,
customDriverDir: String?,
@@ -252,11 +252,18 @@ object NativeLibrary {
external fun initializeSystem(reload: Boolean)
external fun defaultCPUCore(): Int
/**
* Begins emulation.
*/
external fun run(path: String?)
/**
* Begins emulation from the specified savestate.
*/
external fun run(path: String?, savestatePath: String?, deleteSavestate: Boolean)
// Surface Handling
external fun surfaceChanged(surf: Surface?)
@@ -293,13 +300,9 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray
/**
* Returns the current CPU backend.
* Notifies the core emulation that the orientation has changed.
*/
external fun getCpuBackend(): String
external fun applySettings()
external fun logSettings()
external fun notifyOrientationChange(layout_option: Int, rotation: Int)
enum class CoreError {
ErrorSystemFiles,
@@ -530,35 +533,6 @@ object NativeLibrary {
*/
external fun isFirmwareAvailable(): Boolean
/**
* Checks the PatchManager for any addons that are available
*
* @param path Path to game file. Can be a [Uri].
* @param programId String representation of a game's program ID
* @return Array of pairs where the first value is the name of an addon and the second is the version
*/
external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>?
/**
* Gets the save location for a specific game
*
* @param programId String representation of a game's program ID
* @return Save data path that may not exist yet
*/
external fun getSavePath(programId: String): String
/**
* Adds a file to the manual filesystem provider in our EmulationSession instance
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or
* a normal path
*/
external fun addFileToFilesystemProvider(path: String)
/**
* Clears all files added to the manual filesystem provider in our EmulationSession instance
*/
external fun clearFilesystemProvider()
/**
* Button type for use in onTouchEvent
*/
@@ -172,7 +172,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
@@ -284,7 +284,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
PictureInPictureParams.Builder {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
@@ -331,7 +331,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
pictureInPictureActions.add(pauseRemoteAction)
}
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
if (BooleanSetting.AUDIO_MUTED.boolean) {
val unmuteIcon = Icon.createWithResource(
this@EmulationActivity,
R.drawable.ic_pip_unmute
@@ -376,7 +376,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val isEmulationActive = emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
pictureInPictureParamsBuilder.setAutoEnterEnabled(
BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive
BooleanSetting.PICTURE_IN_PICTURE.boolean && isEmulationActive
)
}
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
@@ -390,13 +390,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
}
if (intent.action == actionUnmute) {
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
} else if (intent.action == actionMute) {
if (!BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(true)
}
if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
}
buildPictureInPictureParams()
}
@@ -427,9 +423,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} catch (ignored: Exception) {
}
// Always resume audio, since there is no UI button
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
}
@@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Addon
class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) }
}
override fun getItemCount(): Int = currentList.size
override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
holder.bind(currentList[position])
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(addon: Addon) {
binding.root.setOnClickListener {
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
}
binding.title.text = addon.title
binding.version.text = addon.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
addon.enabled = checked
}
binding.addonSwitch.isChecked = addon.enabled
}
}
private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
}
}
}
@@ -15,7 +15,7 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding
import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
@@ -28,7 +28,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
parent: ViewGroup,
viewType: Int
): AppletAdapter.AppletViewHolder {
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@AppletAdapter) }
.also { return AppletViewHolder(it) }
}
@@ -65,7 +65,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
view.findNavController().navigate(action)
}
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
inner class AppletViewHolder(val binding: CardAppletOptionBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var applet: Applet
@@ -42,7 +42,7 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
if (driverViewModel.selectedDriver > position) {
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
}
if (GpuDriverHelper.customDriverSettingData == driverData.second) {
if (GpuDriverHelper.customDriverData == driverData.second) {
driverViewModel.setSelectedDriverIndex(0)
}
driverViewModel.driversToDelete.add(driverData.first)
@@ -1,76 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FolderAdapter.FolderViewHolder {
CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return FolderViewHolder(it) }
}
override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
holder.bind(currentList[position])
inner class FolderViewHolder(val binding: CardFolderBinding) :
RecyclerView.ViewHolder(binding.root) {
private lateinit var gameDir: GameDir
fun bind(gameDir: GameDir) {
this.gameDir = gameDir
binding.apply {
path.text = Uri.parse(gameDir.uriString).path
path.postDelayed(
{
path.isSelected = true
path.ellipsize = TextUtils.TruncateAt.MARQUEE
},
3000
)
buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
.show(
activity.supportFragmentManager,
GameFolderPropertiesDialogFragment.TAG
)
}
buttonDelete.setOnClickListener {
gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
}
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
}
}
@@ -44,20 +44,19 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
class GameAdapter(private val activity: AppCompatActivity) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener,
View.OnLongClickListener {
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
// Create a new view.
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.cardGame.setOnClickListener(this)
binding.cardGame.setOnLongClickListener(this)
// Use that view to create a ViewHolder.
return GameViewHolder(binding)
}
override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
holder.bind(currentList[position])
}
override fun getItemCount(): Int = currentList.size
@@ -126,17 +125,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
}
}
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
view.findNavController().navigate(action)
}
override fun onLongClick(view: View): Boolean {
val holder = view.tag as GameViewHolder
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
view.findNavController().navigate(action)
return true
}
inner class GameViewHolder(val binding: CardGameBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var game: Game
@@ -165,7 +157,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
return oldItem.programId == newItem.programId
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
@@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
class GamePropertiesAdapter(
private val viewLifecycle: LifecycleOwner,
private var properties: List<GameProperty>
) :
RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): GamePropertyViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
PropertyType.Submenu.ordinal -> {
SubmenuPropertyViewHolder(
CardSimpleOutlinedBinding.inflate(
inflater,
parent,
false
)
)
}
else -> InstallablePropertyViewHolder(
CardInstallableIconBinding.inflate(
inflater,
parent,
false
)
)
}
}
override fun getItemCount(): Int = properties.size
override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
holder.bind(properties[position])
override fun getItemViewType(position: Int): Int {
return when (properties[position]) {
is SubmenuProperty -> PropertyType.Submenu.ordinal
else -> PropertyType.Installable.ordinal
}
}
sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(property: GameProperty)
}
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
GamePropertyViewHolder(binding.root) {
override fun bind(property: GameProperty) {
val submenuProperty = property as SubmenuProperty
binding.root.setOnClickListener {
submenuProperty.action.invoke()
}
binding.title.setText(submenuProperty.titleId)
binding.description.setText(submenuProperty.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
submenuProperty.iconId,
binding.icon.context.theme
)
)
binding.details.postDelayed({
binding.details.isSelected = true
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
}, 3000)
if (submenuProperty.details != null) {
binding.details.visibility = View.VISIBLE
binding.details.text = submenuProperty.details.invoke()
} else if (submenuProperty.detailsFlow != null) {
binding.details.visibility = View.VISIBLE
viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
submenuProperty.detailsFlow.collect { binding.details.text = it }
}
}
} else {
binding.details.visibility = View.GONE
}
}
}
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
GamePropertyViewHolder(binding.root) {
override fun bind(property: GameProperty) {
val installableProperty = property as InstallableProperty
binding.title.setText(installableProperty.titleId)
binding.description.setText(installableProperty.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
installableProperty.iconId,
binding.icon.context.theme
)
)
if (installableProperty.install != null) {
binding.buttonInstall.visibility = View.VISIBLE
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
}
if (installableProperty.export != null) {
binding.buttonExport.visibility = View.VISIBLE
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
}
}
}
enum class PropertyType {
Submenu,
Installable
}
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting {
fun getBoolean(needsGlobal: Boolean = false): Boolean
val boolean: Boolean
fun setBoolean(value: Boolean)
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractByteSetting : AbstractSetting {
fun getByte(needsGlobal: Boolean = false): Byte
val byte: Byte
fun setByte(value: Byte)
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting {
fun getFloat(needsGlobal: Boolean = false): Float
val float: Float
fun setFloat(value: Float)
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting {
fun getInt(needsGlobal: Boolean = false): Int
val int: Int
fun setInt(value: Int)
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractLongSetting : AbstractSetting {
fun getLong(needsGlobal: Boolean = false): Long
val long: Long
fun setLong(value: Long)
}
@@ -7,7 +7,12 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
interface AbstractSetting {
val key: String
val category: Settings.Category
val defaultValue: Any
val androidDefault: Any?
get() = null
val valueAsString: String
get() = ""
val isRuntimeModifiable: Boolean
get() = NativeConfig.getIsRuntimeModifiable(key)
@@ -15,17 +20,5 @@ interface AbstractSetting {
val pairedSettingKey: String
get() = NativeConfig.getPairedSettingKey(key)
val isSwitchable: Boolean
get() = NativeConfig.getIsSwitchable(key)
var global: Boolean
get() = NativeConfig.usingGlobal(key)
set(value) = NativeConfig.setGlobal(key, value)
val isSaveable: Boolean
get() = NativeConfig.getIsSaveable(key)
fun getValueAsString(needsGlobal: Boolean = false): String
fun reset()
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractShortSetting : AbstractSetting {
fun getShort(needsGlobal: Boolean = false): Short
val short: Short
fun setShort(value: Short)
}
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting {
fun getString(needsGlobal: Boolean = false): String
val string: String
fun setString(value: String)
}
@@ -5,34 +5,36 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
AUDIO_MUTED("audio_muted"),
CPU_DEBUG_MODE("cpu_debug_mode"),
FASTMEM("cpuopt_fastmem"),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_DOCKED_MODE("use_docked_mode"),
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"),
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
RENDERER_DEBUG("debug"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled");
enum class BooleanSetting(
override val key: String,
override val category: Settings.Category,
override val androidDefault: Boolean? = null
) : AbstractBooleanSetting {
AUDIO_MUTED("audio_muted", Settings.Category.Audio),
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
RENDERER_DEBUG("debug", Settings.Category.Renderer),
PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal)
override val boolean: Boolean
get() = NativeConfig.getBoolean(key, false)
override fun setBoolean(value: Boolean) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setBoolean(key, value)
override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
override val defaultValue: Boolean by lazy {
androidDefault ?: NativeConfig.getBoolean(key, true)
}
override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() }
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString()
override val valueAsString: String
get() = if (boolean) "1" else "0"
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ByteSetting(override val key: String) : AbstractByteSetting {
AUDIO_VOLUME("volume");
enum class ByteSetting(
override val key: String,
override val category: Settings.Category
) : AbstractByteSetting {
AUDIO_VOLUME("volume", Settings.Category.Audio);
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
override val byte: Byte
get() = NativeConfig.getByte(key, false)
override fun setByte(value: Byte) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setByte(key, value)
}
override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
override val defaultValue: Byte by lazy { NativeConfig.getDefaultToString(key).toByte() }
override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
override fun getValueAsString(needsGlobal: Boolean): String = getByte(needsGlobal).toString()
override val valueAsString: String
get() = byte.toString()
override fun reset() = NativeConfig.setByte(key, defaultValue)
}
@@ -5,22 +5,22 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class FloatSetting(override val key: String) : AbstractFloatSetting {
enum class FloatSetting(
override val key: String,
override val category: Settings.Category
) : AbstractFloatSetting {
// No float settings currently exist
EMPTY_SETTING("");
EMPTY_SETTING("", Settings.Category.UiGeneral);
override fun getFloat(needsGlobal: Boolean): Float = NativeConfig.getFloat(key, false)
override val float: Float
get() = NativeConfig.getFloat(key, false)
override fun setFloat(value: Float) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setFloat(key, value)
}
override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
override val defaultValue: Float by lazy { NativeConfig.getDefaultToString(key).toFloat() }
override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
override fun getValueAsString(needsGlobal: Boolean): String = getFloat(needsGlobal).toString()
override val valueAsString: String
get() = float.toString()
override fun reset() = NativeConfig.setFloat(key, defaultValue)
}
@@ -5,33 +5,35 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class IntSetting(override val key: String) : AbstractIntSetting {
CPU_BACKEND("cpu_backend"),
CPU_ACCURACY("cpu_accuracy"),
REGION_INDEX("region_index"),
LANGUAGE_INDEX("language_index"),
RENDERER_BACKEND("backend"),
RENDERER_ACCURACY("gpu_accuracy"),
RENDERER_RESOLUTION("resolution_setup"),
RENDERER_VSYNC("use_vsync"),
RENDERER_SCALING_FILTER("scaling_filter"),
RENDERER_ANTI_ALIASING("anti_aliasing"),
RENDERER_SCREEN_LAYOUT("screen_layout"),
RENDERER_ASPECT_RATIO("aspect_ratio"),
AUDIO_OUTPUT_ENGINE("output_engine");
enum class IntSetting(
override val key: String,
override val category: Settings.Category,
override val androidDefault: Int? = null
) : AbstractIntSetting {
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
REGION_INDEX("region_index", Settings.Category.System),
LANGUAGE_INDEX("language_index", Settings.Category.System),
RENDERER_BACKEND("backend", Settings.Category.Renderer),
RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
override val int: Int
get() = NativeConfig.getInt(key, false)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setInt(key, value)
override fun setInt(value: Int) = NativeConfig.setInt(key, value)
override val defaultValue: Int by lazy {
androidDefault ?: NativeConfig.getInt(key, true)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override val valueAsString: String
get() = int.toString()
override fun reset() = NativeConfig.setInt(key, defaultValue)
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class LongSetting(override val key: String) : AbstractLongSetting {
CUSTOM_RTC("custom_rtc");
enum class LongSetting(
override val key: String,
override val category: Settings.Category
) : AbstractLongSetting {
CUSTOM_RTC("custom_rtc", Settings.Category.System);
override fun getLong(needsGlobal: Boolean): Long = NativeConfig.getLong(key, needsGlobal)
override val long: Long
get() = NativeConfig.getLong(key, false)
override fun setLong(value: Long) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setLong(key, value)
}
override fun setLong(value: Long) = NativeConfig.setLong(key, value)
override val defaultValue: Long by lazy { NativeConfig.getDefaultToString(key).toLong() }
override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
override fun getValueAsString(needsGlobal: Boolean): String = getLong(needsGlobal).toString()
override val valueAsString: String
get() = long.toString()
override fun reset() = NativeConfig.setLong(key, defaultValue)
}
@@ -3,14 +3,89 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
import android.widget.Toast
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.NativeConfig
object Settings {
private val context get() = YuzuApplication.appContext
fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
Toast.makeText(
context,
context.getString(R.string.ini_saved),
Toast.LENGTH_SHORT
).show()
NativeConfig.saveSettings()
} else {
// TODO: Save custom game settings
Toast.makeText(
context,
context.getString(R.string.gameid_saved, gameId),
Toast.LENGTH_SHORT
).show()
}
}
enum class Category {
Android,
Audio,
Core,
Cpu,
CpuDebug,
CpuUnsafe,
Renderer,
RendererAdvanced,
RendererDebug,
System,
SystemAudio,
DataStorage,
Debugging,
DebuggingGraphics,
Miscellaneous,
Network,
WebService,
AddOns,
Controls,
Ui,
UiGeneral,
UiLayout,
UiGameList,
Screenshots,
Shortcuts,
Multiplayer,
Services,
Paths,
MaxEnum
}
val settingsList = listOf<AbstractSetting>(
*BooleanSetting.values(),
*ByteSetting.values(),
*ShortSetting.values(),
*IntSetting.values(),
*FloatSetting.values(),
*LongSetting.values(),
*StringSetting.values()
)
const val SECTION_GENERAL = "General"
const val SECTION_SYSTEM = "System"
const val SECTION_RENDERER = "Renderer"
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_CPU(R.string.cpu),
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit");
enum class ShortSetting(
override val key: String,
override val category: Settings.Category
) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
override val short: Short
get() = NativeConfig.getShort(key, false)
override fun setShort(value: Short) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setShort(key, value)
}
override fun setShort(value: Short) = NativeConfig.setShort(key, value)
override val defaultValue: Short by lazy { NativeConfig.getDefaultToString(key).toShort() }
override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override val valueAsString: String
get() = short.toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}
@@ -5,21 +5,22 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path");
enum class StringSetting(
override val key: String,
override val category: Settings.Category
) : AbstractStringSetting {
// No string settings currently exist
EMPTY_SETTING("", Settings.Category.UiGeneral);
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
override val string: String
get() = NativeConfig.getString(key, false)
override fun setString(value: String) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setString(key, value)
}
override fun setString(value: String) = NativeConfig.setString(key, value)
override val defaultValue: String by lazy { NativeConfig.getDefaultToString(key) }
override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
override fun getValueAsString(needsGlobal: Boolean): String = getString(needsGlobal)
override val valueAsString: String
get() = string
override fun reset() = NativeConfig.setString(key, defaultValue)
}
@@ -12,6 +12,7 @@ class DateTimeSetting(
) : SettingsItem(longSetting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
fun setValue(value: Long) = (setting as AbstractLongSetting).setLong(value)
var value: Long
get() = longSetting.long
set(value) = (setting as AbstractLongSetting).setLong(value)
}
@@ -11,8 +11,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -30,26 +30,10 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
// Can't edit settings that aren't saveable in per-game config even if they are switchable
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
return false
}
if (!NativeLibrary.isRunning()) return true
// Prevent editing settings that were modified in per-game config while editing global
// config
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
return false
}
return setting.isRuntimeModifiable
}
val needsRuntimeGlobal: Boolean
get() = NativeLibrary.isRunning() && !setting.global &&
!NativeConfig.isPerGameConfigLoaded()
companion object {
const val TYPE_HEADER = 0
const val TYPE_SWITCH = 1
@@ -64,9 +48,8 @@ abstract class SettingsItem(
val emptySetting = object : AbstractSetting {
override val key: String = ""
override val category: Settings.Category = Settings.Category.Ui
override val defaultValue: Any = false
override val isSaveable = true
override fun getValueAsString(needsGlobal: Boolean): String = ""
override fun reset() {}
}
@@ -94,15 +77,6 @@ abstract class SettingsItem(
"%"
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,
R.string.cpu_backend,
0,
R.array.cpuBackendArm64Names,
R.array.cpuBackendArm64Values
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_ACCURACY,
@@ -287,9 +261,9 @@ abstract class SettingsItem(
)
val fastmem = object : AbstractBooleanSetting {
override fun getBoolean(needsGlobal: Boolean): Boolean =
BooleanSetting.FASTMEM.getBoolean() &&
BooleanSetting.FASTMEM_EXCLUSIVES.getBoolean()
override val boolean: Boolean
get() =
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
override fun setBoolean(value: Boolean) {
BooleanSetting.FASTMEM.setBoolean(value)
@@ -297,24 +271,9 @@ abstract class SettingsItem(
}
override val key: String = FASTMEM_COMBINED
override val category = Settings.Category.Cpu
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Boolean = true
override val isSwitchable: Boolean = true
override var global: Boolean
get() {
return BooleanSetting.FASTMEM.global &&
BooleanSetting.FASTMEM_EXCLUSIVES.global
}
set(value) {
BooleanSetting.FASTMEM.global = value
BooleanSetting.FASTMEM_EXCLUSIVES.global = value
}
override val isSaveable = true
override fun getValueAsString(needsGlobal: Boolean): String =
getBoolean().toString()
override fun reset() = setBoolean(defaultValue)
}
put(SwitchSetting(fastmem, R.string.fastmem, 0))
@@ -15,11 +15,16 @@ class SingleChoiceSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE
fun getSelectedValue(needsGlobal: Boolean = false) =
when (setting) {
is AbstractIntSetting -> setting.getInt(needsGlobal)
else -> -1
var selectedValue: Int
get() {
return when (setting) {
is AbstractIntSetting -> setting.int
else -> -1
}
}
set(value) {
when (setting) {
is AbstractIntSetting -> setting.setInt(value)
}
}
fun setSelectedValue(value: Int) = (setting as AbstractIntSetting).setInt(value)
}
@@ -20,20 +20,22 @@ class SliderSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
fun getSelectedValue(needsGlobal: Boolean = false) =
when (setting) {
is AbstractByteSetting -> setting.getByte(needsGlobal).toInt()
is AbstractShortSetting -> setting.getShort(needsGlobal).toInt()
is AbstractIntSetting -> setting.getInt(needsGlobal)
is AbstractFloatSetting -> setting.getFloat(needsGlobal).roundToInt()
else -> -1
var selectedValue: Int
get() {
return when (setting) {
is AbstractByteSetting -> setting.byte.toInt()
is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt()
else -> -1
}
}
fun setSelectedValue(value: Int) =
when (setting) {
is AbstractByteSetting -> setting.setByte(value.toByte())
is AbstractShortSetting -> setting.setShort(value.toShort())
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
else -> (setting as AbstractIntSetting).setInt(value)
set(value) {
when (setting) {
is AbstractByteSetting -> setting.setByte(value.toByte())
is AbstractShortSetting -> setting.setShort(value.toShort())
is AbstractIntSetting -> setting.setInt(value)
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
}
}
}
@@ -17,13 +17,14 @@ class StringSingleChoiceSetting(
fun getValueAt(index: Int): String =
if (index >= 0 && index < values.size) values[index] else ""
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
fun setSelectedValue(value: String) = stringSetting.setString(value)
var selectedValue: String
get() = stringSetting.string
set(value) = stringSetting.setString(value)
val selectValueIndex: Int
get() {
for (i in values.indices) {
if (values[i] == getSelectedValue()) {
if (values[i] == selectedValue) {
return i
}
}
@@ -14,18 +14,18 @@ class SwitchSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
return when (setting) {
is AbstractIntSetting -> setting.getInt(needsGlobal) == 1
is AbstractBooleanSetting -> setting.getBoolean(needsGlobal)
else -> false
var checked: Boolean
get() {
return when (setting) {
is AbstractIntSetting -> setting.int == 1
is AbstractBooleanSetting -> setting.boolean
else -> false
}
}
}
fun setChecked(value: Boolean) {
when (setting) {
is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
is AbstractBooleanSetting -> setting.setBoolean(value)
set(value) {
when (setting) {
is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
is AbstractBooleanSetting -> setting.setBoolean(value)
}
}
}
}
@@ -21,10 +21,10 @@ import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel
@@ -45,9 +45,6 @@ class SettingsActivity : AppCompatActivity() {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
SettingsFile.loadCustomConfig(args.game!!)
}
settingsViewModel.game = args.game
val navHostFragment =
@@ -56,6 +53,10 @@ class SettingsActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (savedInstanceState != null) {
settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
}
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
@@ -126,44 +127,49 @@ class SettingsActivity : AppCompatActivity() {
}
}
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
}
override fun onStart() {
super.onStart()
// TODO: Load custom settings contextually
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
}
/**
* If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted. So we kick off an
* IntentService which will do so on a background thread.
*/
override fun onStop() {
super.onStop()
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
if (isFinishing) {
NativeLibrary.applySettings()
if (args.game == null) {
NativeConfig.saveGlobalConfig()
} else if (NativeConfig.isPerGameConfigLoaded()) {
NativeLibrary.logSettings()
NativeConfig.savePerGameConfig()
NativeConfig.unloadPerGameConfig()
}
if (isFinishing && settingsViewModel.shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
Settings.saveSettings()
}
}
override fun onDestroy() {
settingsViewModel.clear()
super.onDestroy()
}
fun onSettingsReset() {
// Prevents saving to a non-existent settings file
settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI
if (args.game == null) {
NativeConfig.unloadGlobalConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
NativeConfig.initializeGlobalConfig()
} else {
NativeConfig.unloadPerGameConfig()
val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
NativeConfig.unloadConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
NativeConfig.initializeConfig()
Toast.makeText(
applicationContext,
@@ -188,4 +194,8 @@ class SettingsActivity : AppCompatActivity() {
windowInsets
}
}
companion object {
private const val KEY_SHOULD_SAVE = "should_save"
}
}
@@ -102,10 +102,10 @@ class SettingsAdapter(
return currentList[position].type
}
fun onBooleanClick(item: SwitchSetting, checked: Boolean, position: Int) {
item.setChecked(checked)
notifyItemChanged(position)
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.checked = checked
settingsViewModel.setShouldReloadSettingsList(true)
settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@@ -127,7 +127,7 @@ class SettingsAdapter(
}
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
val storedTime = item.getValue() * 1000
val storedTime = item.value * 1000
// Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance()
@@ -160,9 +160,10 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
if (item.getValue() != epochTime) {
if (item.value != epochTime) {
settingsViewModel.shouldSave = true
notifyItemChanged(position)
item.setValue(epochTime)
item.value = epochTime
}
}
datePicker.show(
@@ -196,12 +197,6 @@ class SettingsAdapter(
return true
}
fun onClearClick(item: SettingsItem, position: Int) {
item.setting.global = true
notifyItemChanged(position)
settingsViewModel.setShouldReloadSettingsList(true)
}
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
return oldItem.setting.key == newItem.setting.key
@@ -66,13 +66,7 @@ class SettingsFragment : Fragment() {
args.menuTag
)
binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
args.game != null
) {
args.game!!.title
} else {
getString(args.menuTag.titleId)
}
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
binding.listSettings.apply {
adapter = settingsAdapter
layoutManager = LinearLayoutManager(requireContext())
@@ -7,7 +7,6 @@ import android.content.SharedPreferences
import android.os.Build
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@@ -32,27 +31,12 @@ class SettingsFragmentPresenter(
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
// Extension for altering settings list based on each setting's properties
// Extension for populating settings list based on paired settings
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
if (settingsViewModel.game != null && !item.setting.isSwitchable) {
return
}
if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) {
item.setting.global = true
}
val pairedSettingKey = item.setting.pairedSettingKey
if (pairedSettingKey.isNotEmpty()) {
val pairedSettingValue = NativeConfig.getBoolean(
pairedSettingKey,
if (NativeLibrary.isRunning() && !NativeConfig.isPerGameConfigLoaded()) {
!NativeConfig.usingGlobal(pairedSettingKey)
} else {
NativeConfig.usingGlobal(pairedSettingKey)
}
)
val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
if (!pairedSettingValue) return
}
add(item)
@@ -169,8 +153,8 @@ class SettingsFragmentPresenter(
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_THEME, 0)
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0)
override fun setInt(value: Int) {
preferences.edit()
@@ -180,8 +164,8 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_THEME
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override val defaultValue: Int = 0
override fun reset() {
preferences.edit()
@@ -213,8 +197,8 @@ class SettingsFragmentPresenter(
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_THEME_MODE, -1)
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
override fun setInt(value: Int) {
preferences.edit()
@@ -224,8 +208,8 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_THEME_MODE
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override val defaultValue: Int = -1
override fun reset() {
preferences.edit()
@@ -246,8 +230,8 @@ class SettingsFragmentPresenter(
)
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
override fun getBoolean(needsGlobal: Boolean): Boolean =
preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
override fun setBoolean(value: Boolean) {
preferences.edit()
@@ -257,10 +241,8 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_BLACK_BACKGROUNDS
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String =
getBoolean().toString()
override val defaultValue: Boolean = false
override fun reset() {
preferences.edit()
@@ -287,7 +269,6 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
add(IntSetting.CPU_BACKEND.key)
add(IntSetting.CPU_ACCURACY.key)
add(BooleanSetting.CPU_DEBUG_MODE.key)
add(SettingsItem.FASTMEM_COMBINED)
@@ -13,7 +13,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -30,23 +29,12 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
binding.textSettingValue.visibility = View.VISIBLE
val epochTime = setting.getValue()
val epochTime = setting.value
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime)
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -38,7 +38,6 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
binding.buttonClear.visibility = View.GONE
setStyle(setting.isEditable, binding)
}
@@ -41,7 +41,6 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.textSettingValue.alpha = opacity
binding.buttonClear.isEnabled = isEditable
}
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
@@ -49,6 +48,5 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
val opacity = if (isEditable) 1.0f else 0.5f
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.buttonClear.isEnabled = isEditable
}
}
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -30,31 +29,20 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
val resMgr = binding.textSettingValue.context.resources
val values = resMgr.getIntArray(item.valuesId)
for (i in values.indices) {
if (values[i] == item.getSelectedValue()) {
if (values[i] == item.selectedValue) {
binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
break
}
}
} else if (item is StringSingleChoiceSetting) {
for (i in item.values.indices) {
if (item.values[i] == item.getSelectedValue()) {
if (item.values[i] == item.selectedValue) {
binding.textSettingValue.text = item.choices[i]
break
}
}
}
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -27,21 +26,10 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = String.format(
binding.textSettingValue.context.getString(R.string.value_with_units),
setting.getSelectedValue(),
setting.selectedValue,
setting.units
)
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -37,7 +37,6 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
binding.buttonClear.visibility = View.GONE
}
override fun onClick(clicked: View) {
@@ -9,7 +9,6 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -28,20 +27,9 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
}
binding.switchWidget.setOnCheckedChangeListener(null)
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
binding.switchWidget.isChecked = setting.checked
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
}
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
adapter.onBooleanClick(item, binding.switchWidget.isChecked)
}
setStyle(setting.isEditable, binding)
@@ -3,27 +3,15 @@
package org.yuzu.yuzu_emu.features.settings.utils
import android.net.Uri
import org.yuzu.yuzu_emu.model.Game
import java.io.*
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
*/
object SettingsFile {
const val FILE_NAME_CONFIG = "config.ini"
const val FILE_NAME_CONFIG = "config"
fun getSettingsFile(fileName: String): File =
File(DirectoryInitialization.userDirectory + "/config/" + fileName)
fun getCustomSettingsFile(game: Game): File =
File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini")
fun loadCustomConfig(game: Game) {
val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.programId, fileName)
}
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
}
@@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
class AddGameFolderDialogFragment : DialogFragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogAddFolderBinding.inflate(layoutInflater)
val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
if (folderUriString == null) {
dismiss()
}
binding.path.text = Uri.parse(folderUriString).path
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.add_game_folder)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
homeViewModel.setGamesDirSelected(true)
gamesViewModel.addFolder(newGameDir)
}
.setNegativeButton(android.R.string.cancel, null)
.setView(binding.root)
.show()
}
companion object {
const val TAG = "AddGameFolderDialogFragment"
private const val FOLDER_URI_STRING = "FolderUriString"
fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
val args = Bundle()
args.putString(FOLDER_URI_STRING, folderUriString)
val fragment = AddGameFolderDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -1,214 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AddonAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.AddonUtil
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
import java.io.File
class AddonsFragment : Fragment() {
private var _binding: FragmentAddonsBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val addonViewModel: AddonViewModel by activityViewModels()
private val args by navArgs<AddonsFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addonViewModel.onOpenAddons(args.game)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAddonsBinding.inflate(inflater)
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.toolbarAddons.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title)
binding.listAddons.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = AddonAdapter()
}
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.addonList.collect {
(binding.listAddons.adapter as AddonAdapter).submitList(it)
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.showModInstallPicker.collect {
if (it) {
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
addonViewModel.showModInstallPicker(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.showModNoticeDialog.collect {
if (it) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.addon_notice,
descriptionId = R.string.addon_notice_description,
positiveAction = { addonViewModel.showModInstallPicker(true) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
addonViewModel.showModNoticeDialog(false)
}
}
}
}
}
binding.buttonInstall.setOnClickListener {
ContentTypeSelectionDialogFragment().show(
parentFragmentManager,
ContentTypeSelectionDialogFragment.TAG
)
}
setInsets()
}
override fun onResume() {
super.onResume()
addonViewModel.refreshAddons()
}
override fun onDestroy() {
super.onDestroy()
addonViewModel.onCloseAddons()
}
val installAddon =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) {
return@registerForActivityResult
}
val externalAddonDirectory = DocumentFile.fromTreeUri(requireContext(), result)
if (externalAddonDirectory == null) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.invalid_directory,
descriptionId = R.string.invalid_directory_description
).show(parentFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
val isValid = externalAddonDirectory.listFiles()
.any { AddonUtil.validAddonDirectories.contains(it.name) }
val errorMessage = MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.invalid_directory,
descriptionId = R.string.invalid_directory_description
)
if (isValid) {
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_game_content,
false
) {
val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName)
try {
externalAddonDirectory.copyFilesTo(internalAddonDirectory)
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
} else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarAddons.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarAddons.layoutParams = mlpToolbar
val mlpAddonsList = binding.listAddons.layoutParams as ViewGroup.MarginLayoutParams
mlpAddonsList.leftMargin = leftInsets
mlpAddonsList.rightMargin = rightInsets
binding.listAddons.layoutParams = mlpAddonsList
binding.listAddons.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonInstall.layoutParams = mlpFab
windowInsets
}
}
@@ -1,68 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class ContentTypeSelectionDialogFragment : DialogFragment() {
private val addonViewModel: AddonViewModel by activityViewModels()
private val preferences get() =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
private var selectedItem = 0
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val launchOptions =
arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats))
if (savedInstanceState != null) {
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
}
val mainActivity = requireActivity() as MainActivity
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.select_content_type)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (selectedItem) {
0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*"))
else -> {
if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) {
preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply()
addonViewModel.showModNoticeDialog(true)
return@setPositiveButton
}
addonViewModel.showModInstallPicker(true)
}
}
}
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
selectedItem = i
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SELECTED_ITEM, selectedItem)
}
companion object {
const val TAG = "ContentTypeSelectionDialogFragment"
private const val SELECTED_ITEM = "SelectedItem"
private const val MOD_NOTICE_SEEN = "ModNoticeSeen"
}
}
@@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
@@ -37,8 +36,6 @@ class DriverManagerFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<DriverManagerFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
@@ -60,9 +57,7 @@ class DriverManagerFragment : Fragment() {
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
driverViewModel.onOpenDriverManager(args.game)
if (!driverViewModel.isInteractionAllowed.value) {
if (!driverViewModel.isInteractionAllowed) {
DriversLoadingDialogFragment().show(
childFragmentManager,
DriversLoadingDialogFragment.TAG
@@ -107,9 +102,10 @@ class DriverManagerFragment : Fragment() {
setInsets()
}
override fun onDestroy() {
super.onDestroy()
driverViewModel.onCloseDriverManager(args.game)
// Start installing requested driver
override fun onStop() {
super.onStop()
driverViewModel.onCloseDriverManager()
}
private fun setInsets() =
@@ -47,9 +47,25 @@ class DriversLoadingDialogFragment : DialogFragment() {
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
driverViewModel.areDriversLoading.collect { checkForDismiss() }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDriverReady.collect { checkForDismiss() }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
}
}
}
}
private fun checkForDismiss() {
if (driverViewModel.isInteractionAllowed) {
dismiss()
}
}
@@ -52,7 +52,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel
@@ -128,17 +127,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return
}
// Always load custom settings when launching a game from an intent
if (args.custom || intentGame != null) {
SettingsFile.loadCustomConfig(game)
NativeConfig.unloadPerGameConfig()
} else {
NativeConfig.reloadGlobalConfig()
}
// Install the selected driver asynchronously as the game starts
driverViewModel.onLaunchGame()
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -229,15 +217,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
R.id.menu_settings_per_game -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
args.game,
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
true
}
R.id.menu_overlay_controls -> {
showOverlayOptions()
true
@@ -353,9 +332,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isInteractionAllowed.collect {
if (it) {
onEmulationStart()
driverViewModel.isDriverReady.collect {
if (it && !emulationState.isRunning) {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
}
}
}
@@ -363,18 +348,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private fun onEmulationStart() {
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (_binding == null) {
@@ -441,10 +414,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats()
val cpuBackend = NativeLibrary.getCpuBackend()
if (_binding != null) {
binding.showFpsText.text =
String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
@@ -462,7 +433,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() {
emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape ->
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
@@ -644,7 +615,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity")
private fun startConfiguringControls() {
// Lock the current orientation to prevent editing inconsistencies
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -662,7 +633,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.doneControlConfig.visibility = View.GONE
binding.surfaceInputOverlay.setIsInEditMode(false)
// Unlock the orientation if it was locked for editing
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
@@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class GameFolderPropertiesDialogFragment : DialogFragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
private var deepScan = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
// Restore checkbox state
binding.deepScanSwitch.isChecked =
savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
// Ensure that we can get the checkbox state even if the view is destroyed
deepScan = binding.deepScanSwitch.isChecked
binding.deepScanSwitch.setOnClickListener {
deepScan = binding.deepScanSwitch.isChecked
}
return MaterialAlertDialogBuilder(requireContext())
.setView(binding.root)
.setTitle(R.string.game_folder_properties)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
if (folderIndex != -1) {
gamesViewModel.folders.value[folderIndex].deepScan =
binding.deepScanSwitch.isChecked
gamesViewModel.updateGameDirs()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(DEEP_SCAN, deepScan)
}
companion object {
const val TAG = "GameFolderPropertiesDialogFragment"
private const val GAME_DIR = "GameDir"
private const val DEEP_SCAN = "DeepScan"
fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
val args = Bundle()
args.putParcelable(GAME_DIR, gameDir)
val fragment = GameFolderPropertiesDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -1,128 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FolderAdapter
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class GameFoldersFragment : Fragment() {
private var _binding: FragmentFoldersBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
gamesViewModel.onOpenGameFoldersFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFoldersBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarFolders.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
binding.listFolders.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.grid_columns)
)
adapter = FolderAdapter(requireActivity(), gamesViewModel)
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.folders.collect {
(binding.listFolders.adapter as FolderAdapter).submitList(it)
}
}
}
val mainActivity = requireActivity() as MainActivity
binding.buttonAdd.setOnClickListener {
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
setInsets()
}
override fun onStop() {
super.onStop()
gamesViewModel.onCloseGameFoldersFragment()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarFolders.layoutParams = mlpToolbar
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonAdd.layoutParams = mlpFab
val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpListFolders.leftMargin = leftInsets
mlpListFolders.rightMargin = rightInsets
binding.listFolders.layoutParams = mlpListFolders
binding.listFolders.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
}
@@ -1,148 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.GameMetadata
class GameInfoFragment : Fragment() {
private var _binding: FragmentGameInfoBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val args by navArgs<GameInfoFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
// Check for an up-to-date version string
args.game.version = GameMetadata.getVersion(args.game.path, true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentGameInfoBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.apply {
toolbarInfo.title = args.game.title
toolbarInfo.setNavigationOnClickListener {
view.findNavController().popBackStack()
}
val pathString = Uri.parse(args.game.path).path ?: ""
path.setHint(R.string.path)
pathField.setText(pathString)
pathField.setOnClickListener { copyToClipboard(getString(R.string.path), pathString) }
programId.setHint(R.string.program_id)
programIdField.setText(args.game.programIdHex)
programIdField.setOnClickListener {
copyToClipboard(getString(R.string.program_id), args.game.programIdHex)
}
if (args.game.developer.isNotEmpty()) {
developer.setHint(R.string.developer)
developerField.setText(args.game.developer)
developerField.setOnClickListener {
copyToClipboard(getString(R.string.developer), args.game.developer)
}
} else {
developer.visibility = View.GONE
}
version.setHint(R.string.version)
versionField.setText(args.game.version)
versionField.setOnClickListener {
copyToClipboard(getString(R.string.version), args.game.version)
}
buttonCopy.setOnClickListener {
val details = """
${args.game.title}
${getString(R.string.path)} - $pathString
${getString(R.string.program_id)} - ${args.game.programIdHex}
${getString(R.string.developer)} - ${args.game.developer}
${getString(R.string.version)} - ${args.game.version}
""".trimIndent()
copyToClipboard(args.game.title, details)
}
}
setInsets()
}
private fun copyToClipboard(label: String, body: String) {
val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, body)
clipBoard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
Toast.makeText(
requireContext(),
R.string.copied_to_clipboard,
Toast.LENGTH_SHORT
).show()
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarInfo.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarInfo.layoutParams = mlpToolbar
val mlpScrollAbout = binding.scrollInfo.layoutParams as ViewGroup.MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.scrollInfo.layoutParams = mlpScrollAbout
binding.contentInfo.updatePadding(bottom = barInsets.bottom)
windowInsets
}
}
@@ -1,456 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
class GamePropertiesFragment : Fragment() {
private var _binding: FragmentGamePropertiesBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<GamePropertiesFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentGamePropertiesBinding.inflate(layoutInflater)
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
binding.buttonBack.setOnClickListener {
view.findNavController().popBackStack()
}
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
binding.title.text = args.game.title
binding.title.postDelayed(
{
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.title.isSelected = true
},
3000
)
binding.buttonStart.setOnClickListener {
LaunchGameDialogFragment.newInstance(args.game)
.show(childFragmentManager, LaunchGameDialogFragment.TAG)
}
reloadList()
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
homeViewModel.openImportSaves.collect {
if (it) {
importSaves.launch(arrayOf("application/zip"))
homeViewModel.setOpenImportSaves(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
homeViewModel.reloadPropertiesList.collect {
if (it) {
reloadList()
homeViewModel.reloadPropertiesList(false)
}
}
}
}
}
setInsets()
}
override fun onDestroy() {
super.onDestroy()
gamesViewModel.reloadGames(true)
}
private fun reloadList() {
_binding ?: return
driverViewModel.updateDriverNameForGame(args.game)
val properties = mutableListOf<GameProperty>().apply {
add(
SubmenuProperty(
R.string.info,
R.string.info_description,
R.drawable.ic_info_outline
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToGameInfoFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
add(
SubmenuProperty(
R.string.preferences_settings,
R.string.per_game_settings_description,
R.drawable.ic_settings
) {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
args.game,
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
}
)
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
SubmenuProperty(
R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
R.drawable.ic_build,
detailsFlow = driverViewModel.selectedDriverTitle
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToDriverManagerFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
}
if (!args.game.isHomebrew) {
add(
SubmenuProperty(
R.string.add_ons,
R.string.add_ons_description,
R.drawable.ic_edit
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToAddonsFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
add(
InstallableProperty(
R.string.save_data,
R.string.save_data_description,
R.drawable.ic_save,
{
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.import_save_warning,
descriptionId = R.string.import_save_warning_description,
positiveAction = { homeViewModel.setOpenImportSaves(true) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
},
if (File(args.game.saveDir).exists()) {
{ exportSaves.launch(args.game.saveZipName) }
} else {
null
}
)
)
val saveDirFile = File(args.game.saveDir)
if (saveDirFile.exists()) {
add(
SubmenuProperty(
R.string.delete_save_data,
R.string.delete_save_data_description,
R.drawable.ic_delete,
action = {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.delete_save_data,
descriptionId = R.string.delete_save_data_warning_description,
positiveAction = {
File(args.game.saveDir).deleteRecursively()
Toast.makeText(
YuzuApplication.appContext,
R.string.save_data_deleted_successfully,
Toast.LENGTH_SHORT
).show()
homeViewModel.reloadPropertiesList(true)
}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
)
)
}
val shaderCacheDir = File(
DirectoryInitialization.userDirectory +
"/shader/" + args.game.settingsName.lowercase()
)
if (shaderCacheDir.exists()) {
add(
SubmenuProperty(
R.string.clear_shader_cache,
R.string.clear_shader_cache_description,
R.drawable.ic_delete,
{
if (shaderCacheDir.exists()) {
val bytes = shaderCacheDir.walkTopDown().filter { it.isFile }
.map { it.length() }.sum()
MemoryUtil.bytesToSizeUnit(bytes.toFloat())
} else {
MemoryUtil.bytesToSizeUnit(0f)
}
}
) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.clear_shader_cache,
descriptionId = R.string.clear_shader_cache_warning_description,
positiveAction = {
shaderCacheDir.deleteRecursively()
Toast.makeText(
YuzuApplication.appContext,
R.string.cleared_shaders_successfully,
Toast.LENGTH_SHORT
).show()
homeViewModel.reloadPropertiesList(true)
}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
)
}
}
}
binding.listProperties.apply {
layoutManager =
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
adapter = GamePropertiesAdapter(viewLifecycleOwner, properties)
}
}
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(args.game)
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val smallLayout = resources.getBoolean(R.bool.small_layout)
if (smallLayout) {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.leftMargin = leftInsets
mlpListAll.rightMargin = rightInsets
binding.listAll.layoutParams = mlpListAll
} else {
if (ViewCompat.getLayoutDirection(binding.root) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.rightMargin = rightInsets
binding.listAll.layoutParams = mlpListAll
val mlpIconLayout =
binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams
mlpIconLayout.topMargin = barInsets.top
mlpIconLayout.leftMargin = leftInsets
binding.iconLayout!!.layoutParams = mlpIconLayout
} else {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.leftMargin = leftInsets
binding.listAll.layoutParams = mlpListAll
val mlpIconLayout =
binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams
mlpIconLayout.topMargin = barInsets.top
mlpIconLayout.rightMargin = rightInsets
binding.iconLayout!!.layoutParams = mlpIconLayout
}
}
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonStart.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonStart.layoutParams = mlpFab
binding.layoutAll.updatePadding(
top = barInsets.top,
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
private val importSaves =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null
if (files != null) {
val savesFolderName = args.game.programIdHex
for (file in files) {
if (file.isDirectory && file.name == savesFolderName) {
savesFolderFile = file
break
}
}
}
if (savesFolderFile != null) {
savesFolder.deleteRecursively()
savesFolder.mkdir()
savesFolderFile.copyRecursively(savesFolder)
savesFolderFile.deleteRecursively()
}
withContext(Dispatchers.Main) {
if (savesFolderFile == null) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.save_file_invalid_zip_structure,
descriptionId = R.string.save_file_invalid_zip_structure_description
).show(parentFragmentManager, MessageDialogFragment.TAG)
return@withContext
}
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.save_file_imported_success),
Toast.LENGTH_LONG
).show()
homeViewModel.reloadPropertiesList(true)
}
cacheSaveDir.deleteRecursively()
} catch (e: Exception) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
/**
* Exports the save file located in the given folder path by creating a zip file and opening a
* file picker to save.
*/
private val exportSaves = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation),
saveLocation.replaceAfterLast("/", ""),
BufferedOutputStream(requireContext().contentResolver.openOutputStream(result))
)
return@newInstance when (zipResult) {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}
@@ -68,9 +68,6 @@ class HomeSettingsFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true)
mainActivity = requireActivity() as MainActivity
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
@@ -94,14 +91,13 @@ class HomeSettingsFragment : Fragment() {
R.string.install_gpu_driver_description,
R.drawable.ic_build,
{
val action = HomeSettingsFragmentDirections
.actionHomeSettingsFragmentToDriverManagerFragment(null)
binding.root.findNavController().navigate(action)
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
},
{ GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported,
R.string.custom_driver_not_supported_description,
driverViewModel.selectedDriverTitle
driverViewModel.selectedDriverMetadata
)
)
add(
@@ -131,13 +127,18 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
R.string.manage_game_folders,
R.string.select_games_folder,
R.string.select_games_folder_description,
R.drawable.ic_add,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
}
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
},
{ true },
0,
0,
homeViewModel.gamesDir
)
)
add(
@@ -216,11 +217,8 @@ class HomeSettingsFragment : Fragment() {
override fun onStart() {
super.onStart()
exitTransition = null
}
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(null)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true)
}
override fun onDestroyView() {
@@ -122,7 +122,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
task: suspend () -> Any
task: () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
@@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.Installable
import org.yuzu.yuzu_emu.ui.main.MainActivity
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class InstallableFragment : Fragment() {
private var _binding: FragmentInstallablesBinding? = null
@@ -73,6 +75,28 @@ class InstallableFragment : Fragment() {
R.string.install_firmware_description,
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
),
if (mainActivity.savesFolderRoot != "") {
Installable(
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
export = {
mainActivity.exportSaves.launch(
"yuzu saves - ${
LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
)
}.zip"
)
}
)
} else {
Installable(
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }
)
},
Installable(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
@@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class LaunchGameDialogFragment : DialogFragment() {
private var selectedItem = 1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val game = requireArguments().parcelable<Game>(GAME)
val launchOptions = arrayOf(getString(R.string.global), getString(R.string.custom))
if (savedInstanceState != null) {
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.launch_options)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val action = HomeNavigationDirections
.actionGlobalEmulationActivity(game, selectedItem != 0)
requireParentFragment().findNavController().navigate(action)
}
.setSingleChoiceItems(launchOptions, 1) { _: DialogInterface, i: Int ->
selectedItem = i
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SELECTED_ITEM, selectedItem)
}
companion object {
const val TAG = "LaunchGameDialogFragment"
const val GAME = "Game"
const val SELECTED_ITEM = "SelectedItem"
fun newInstance(game: Game): LaunchGameDialogFragment {
val args = Bundle()
args.putParcelable(GAME, game)
val fragment = LaunchGameDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -27,31 +27,30 @@ class MessageDialogFragment : DialogFragment() {
val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
val helpLinkId = requireArguments().getInt(HELP_LINK)
val builder = MaterialAlertDialogBuilder(requireContext())
val dialog = MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(R.string.close, null)
if (messageDialogViewModel.positiveAction == null) {
builder.setPositiveButton(R.string.close, null)
} else {
builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
messageDialogViewModel.positiveAction?.invoke()
}.setNegativeButton(android.R.string.cancel, null)
}
if (titleId != 0) builder.setTitle(titleId)
if (titleString.isNotEmpty()) builder.setTitle(titleString)
if (titleId != 0) dialog.setTitle(titleId)
if (titleString.isNotEmpty()) dialog.setTitle(titleString)
if (descriptionId != 0) {
builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
}
if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString)
if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (helpLinkId != 0) {
builder.setNeutralButton(R.string.learn_more) { _, _ ->
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
openLink(getString(helpLinkId))
}
}
return builder.show()
return dialog.show()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
messageDialogViewModel.dismissAction.invoke()
messageDialogViewModel.clear()
}
private fun openLink(link: String) {
@@ -75,7 +74,7 @@ class MessageDialogFragment : DialogFragment() {
descriptionId: Int = 0,
descriptionString: String = "",
helpLinkId: Int = 0,
positiveAction: (() -> Unit)? = null
dismissAction: () -> Unit = {}
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
@@ -86,10 +85,8 @@ class MessageDialogFragment : DialogFragment() {
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
clear()
this.positiveAction = positiveAction
}
ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
dismissAction
dialog.arguments = bundle
return dialog
}
@@ -24,7 +24,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Locale
import org.yuzu.yuzu_emu.R
@@ -61,9 +60,7 @@ class SearchFragment : Fragment() {
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
homeViewModel.setNavigationVisibility(visible = true, animated = false)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (savedInstanceState != null) {
@@ -102,7 +99,7 @@ class SearchFragment : Fragment() {
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.games.collectLatest { filterAndSearch() }
gamesViewModel.games.collect { filterAndSearch() }
}
}
launch {
@@ -52,6 +52,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
settingsViewModel.clickedItem!!.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
settingsViewModel.shouldSave = true
}
.setNegativeButton(android.R.string.cancel, null)
.create()
@@ -70,7 +71,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
sliderBinding = DialogSliderBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as SliderSetting
settingsViewModel.setSliderTextValue(item.getSelectedValue().toFloat(), item.units)
settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
sliderBinding.slider.apply {
valueFrom = item.min.toFloat()
valueTo = item.max.toFloat()
@@ -136,18 +137,25 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
is SingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
val value = getValueForSingleChoiceSelection(scSetting, which)
scSetting.setSelectedValue(value)
if (scSetting.selectedValue != value) {
settingsViewModel.shouldSave = true
}
scSetting.selectedValue = value
}
is StringSingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which)
scSetting.setSelectedValue(value)
if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
scSetting.selectedValue = value
}
is SliderSetting -> {
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
settingsViewModel.shouldSave = true
}
sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
}
}
closeDialog()
@@ -171,7 +179,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
val value = item.getSelectedValue()
val value = item.selectedValue
val valuesId = item.valuesId
if (valuesId > 0) {
val valuesArray = requireContext().resources.getIntArray(valuesId)
@@ -211,7 +219,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
(clickedItem as SliderSetting).getSelectedValue().toFloat()
(clickedItem as SliderSetting).selectedValue.toFloat()
)
}
settingsViewModel.clickedItem = clickedItem
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.fragments
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
@@ -43,7 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() {
@@ -76,8 +75,6 @@ class SetupFragment : Fragment() {
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
@@ -187,7 +184,11 @@ class SetupFragment : Fragment() {
R.string.add_games_warning_description,
R.string.add_games_warning_help,
{
if (NativeConfig.getGameDirs().isNotEmpty()) {
val preferences =
PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
@@ -209,24 +210,12 @@ class SetupFragment : Fragment() {
)
}
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.shouldPageForward.collect {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.gamesDirSelected.collect {
if (it) {
gamesDirCallback.onStepCompleted()
homeViewModel.setGamesDirSelected(false)
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.shouldPageForward.collect {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
}
@@ -304,11 +293,6 @@ class SetupFragment : Fragment() {
setInsets()
}
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (_binding != null) {
@@ -359,6 +343,7 @@ class SetupFragment : Fragment() {
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
mainActivity.processGamesDir(result)
gamesDirCallback.onStepCompleted()
}
}
Loaded 100 of 450 files, more files were not shown because too many files have changed in this diff. Show more