Compare commits

..

3 Commits

Author SHA1 Message Date
Abandoned Cart
7ab4ab16a7 android: Set default rotation to landscape 2023-06-04 14:44:24 -04:00
Abandoned Cart
4767fe993b android: Use the predefined layout values 2023-06-04 10:34:59 -04:00
Abandoned Cart
9440d5eb8d android: Add settings for variable orientation 2023-06-04 10:27:53 -04:00
259 changed files with 2694 additions and 10724 deletions

View File

@@ -2,5 +2,5 @@
; SPDX-License-Identifier: GPL-2.0-or-later
[codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink

View File

@@ -129,11 +129,6 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- name: Set up cache
uses: actions/cache@v3
with:
@@ -165,3 +160,24 @@ jobs:
with:
name: android
path: artifacts/
release:
runs-on: ubuntu-latest
needs: [ android ]
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- uses: actions/download-artifact@v3
- name: Create release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: ${{ github.ref_name }}
draft: false
prerelease: false
- name: Upload artifacts
uses: alexellis/upload-assets@0.4.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_paths: '["./**/*.tar.*","./**/*.AppImage","./**/*.7z","./**/*.zip","./**/*.apk","./**/*.aab"]'

2
.gitignore vendored
View File

@@ -26,8 +26,6 @@ CMakeSettings.json
# OSX global filetypes
# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
.DS_Store
.DS_Store?
._*
.AppleDouble
.LSOverride
.Spotlight-V100

5
.gitmodules vendored
View File

@@ -49,9 +49,6 @@
[submodule "cpp-jwt"]
path = externals/cpp-jwt
url = https://github.com/arun11299/cpp-jwt.git
[submodule "libadrenotools"]
[submodule "externals/libadrenotools"]
path = externals/libadrenotools
url = https://github.com/bylaws/libadrenotools
[submodule "tzdb_to_nx"]
path = externals/nx_tzdb/tzdb_to_nx
url = https://github.com/lat9nq/tzdb_to_nx.git

View File

@@ -59,8 +59,6 @@ option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
# On Android, fetch and compile libcxx before doing anything else
@@ -257,7 +255,7 @@ endif()
# boost asio's concept usage doesn't play nicely with some compilers yet.
add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
if (MSVC)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++20>)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)
# boost still makes use of deprecated result_of.
add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
@@ -489,7 +487,7 @@ if (ENABLE_SDL2)
if (YUZU_USE_BUNDLED_SDL2)
# Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
set(SDL2_VER "SDL2-2.28.0")
set(SDL2_VER "SDL2-2.0.18")
else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
endif()
@@ -509,7 +507,7 @@ if (ENABLE_SDL2)
elseif (YUZU_USE_EXTERNAL_SDL2)
message(STATUS "Using SDL2 from externals.")
else()
find_package(SDL2 2.26.4 REQUIRED)
find_package(SDL2 2.0.18 REQUIRED)
endif()
endif()

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<h4 align="center"><b>yuzu</b> is the world's most popular, open-source, Nintendo Switch emulator — started by the creators of <a href="https://citra-emu.org" target="_blank">Citra</a>.
<br>
It is written in C++ with portability in mind, and we actively maintain builds for Windows, Linux and Android.
It is written in C++ with portability in mind, and we actively maintain builds for Windows and Linux.
</h4>
<p align="center">

View File

@@ -63,9 +63,8 @@ if (YUZU_USE_EXTERNAL_SDL2)
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
# CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
set(SDL_UNUSED_SUBSYSTEMS
File Filesystem
CPUinfo File Filesystem
Locale Power Render)
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
string(TOUPPER ${_SUB} _OPT)
@@ -140,9 +139,6 @@ if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
add_subdirectory(Vulkan-Headers)
endif()
# TZDB (Time Zone Database)
add_subdirectory(nx_tzdb)
if (NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp)
target_include_directories(demangle PUBLIC ./demangle)

2
externals/SDL vendored

View File

@@ -1,101 +0,0 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
add_library(nx_tzdb INTERFACE)
find_program(GIT git)
find_program(GNU_MAKE make)
find_program(DATE_PROG date)
set(CAN_BUILD_NX_TZDB true)
if (NOT GIT)
set(CAN_BUILD_NX_TZDB false)
endif()
if (NOT GNU_MAKE)
set(CAN_BUILD_NX_TZDB false)
endif()
if (NOT DATE_PROG)
set(CAN_BUILD_NX_TZDB false)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
# tzdb_to_nx currently requires a posix-compliant host
# MinGW and Android are handled here due to the executable format being different from the host system
# TODO (lat9nq): cross-compiling support
set(CAN_BUILD_NX_TZDB false)
endif()
set(NX_TZDB_VERSION "220816")
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE})
set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}
STATUS NX_TZDB_DOWNLOAD_STATUS)
list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE)
if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0)
message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})")
endif()
file(ARCHIVE_EXTRACT
INPUT
${NX_TZDB_ARCHIVE}
DESTINATION
${NX_TZDB_ROMFS_DIR})
elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA)
add_subdirectory(tzdb_to_nx)
add_dependencies(nx_tzdb x80e)
set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}")
endif()
target_include_directories(nx_tzdb
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
INTERFACE ${NX_TZDB_INCLUDE_DIR})
function(CreateHeader ZONE_PATH HEADER_NAME)
set(HEADER_PATH "${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h")
add_custom_command(
OUTPUT
${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h
COMMAND
${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/NxTzdbCreateHeader.cmake
${ZONE_PATH}
${HEADER_NAME}
${NX_TZDB_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS
tzdb_template.h.in
NxTzdbCreateHeader.cmake)
target_sources(nx_tzdb PRIVATE ${HEADER_PATH})
endfunction()
CreateHeader(${NX_TZDB_ROMFS_DIR} base)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific)
CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/US us)

View File

@@ -1,8 +0,0 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# CMake does not have a way to list the files in a specific directory,
# so we need this script to do that for us in a platform-agnostic fashion
file(GLOB FILE_LIST LIST_DIRECTORIES false RELATIVE ${CMAKE_SOURCE_DIR} "*")
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${FILE_LIST};")

View File

@@ -1,46 +0,0 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
set(ZONE_PATH ${CMAKE_ARGV3})
set(HEADER_NAME ${CMAKE_ARGV4})
set(NX_TZDB_INCLUDE_DIR ${CMAKE_ARGV5})
set(NX_TZDB_SOURCE_DIR ${CMAKE_ARGV6})
execute_process(
COMMAND ${CMAKE_COMMAND} -P ${NX_TZDB_SOURCE_DIR}/ListFilesInDirectory.cmake
WORKING_DIRECTORY ${ZONE_PATH}
OUTPUT_VARIABLE FILE_LIST)
set(DIRECTORY_NAME ${HEADER_NAME})
set(FILE_DATA "")
foreach(ZONE_FILE ${FILE_LIST})
if (ZONE_FILE STREQUAL "\n")
continue()
endif()
string(APPEND FILE_DATA "{\"${ZONE_FILE}\",\n{")
file(READ ${ZONE_PATH}/${ZONE_FILE} ZONE_DATA HEX)
string(LENGTH "${ZONE_DATA}" ZONE_DATA_LEN)
foreach(I RANGE 0 ${ZONE_DATA_LEN} 2)
math(EXPR BREAK_LINE "(${I} + 2) % 38")
string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA)
if (NOT HEX_DATA)
break()
endif()
string(APPEND FILE_DATA "0x${HEX_DATA},")
if (BREAK_LINE EQUAL 0)
string(APPEND FILE_DATA "\n")
else()
string(APPEND FILE_DATA " ")
endif()
endforeach()
string(APPEND FILE_DATA "}},\n")
endforeach()
file(READ ${NX_TZDB_SOURCE_DIR}/tzdb_template.h.in NX_TZDB_TEMPLATE_H_IN)
file(CONFIGURE OUTPUT ${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h CONTENT "${NX_TZDB_TEMPLATE_H_IN}")

View File

@@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "nx_tzdb/africa.h"
#include "nx_tzdb/america.h"
#include "nx_tzdb/america_argentina.h"
#include "nx_tzdb/america_indiana.h"
#include "nx_tzdb/america_kentucky.h"
#include "nx_tzdb/america_north_dakota.h"
#include "nx_tzdb/antarctica.h"
#include "nx_tzdb/arctic.h"
#include "nx_tzdb/asia.h"
#include "nx_tzdb/atlantic.h"
#include "nx_tzdb/australia.h"
#include "nx_tzdb/base.h"
#include "nx_tzdb/brazil.h"
#include "nx_tzdb/canada.h"
#include "nx_tzdb/chile.h"
#include "nx_tzdb/etc.h"
#include "nx_tzdb/europe.h"
#include "nx_tzdb/indian.h"
#include "nx_tzdb/mexico.h"
#include "nx_tzdb/pacific.h"
#include "nx_tzdb/us.h"
#include "nx_tzdb/zoneinfo.h"

View File

@@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <map>
#include <vector>
namespace NxTzdb {
// clang-format off
const static std::map<const char*, const std::vector<uint8_t>> @DIRECTORY_NAME@ =
{
@FILE_DATA@};
// clang-format on
} // namespace NxTzdb

View File

@@ -43,7 +43,7 @@ if (MSVC)
/Zo
/permissive-
/EHsc
/std:c++20
/std:c++latest
/utf-8
/volatile:iso
/Zc:externConstexpr
@@ -51,10 +51,8 @@ if (MSVC)
/Zc:throwingNew
/GT
# Modules
/experimental:module- # Disable module support explicitly due to conflicts with precompiled headers
# External headers diagnostics
/experimental:external # Enables the external headers options. This option isn't required in Visual Studio 2019 version 16.10 and later
/external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
/external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers

View File

@@ -2,17 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import android.annotation.SuppressLint
import kotlin.collections.setOf
import org.jetbrains.kotlin.konan.properties.Properties
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.8.21"
id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
}
/**
@@ -47,6 +42,16 @@ android {
jniLibs.useLegacyPackaging = true
}
lint {
// This is important as it will run lint but not abort on error
// Lint has some overly obnoxious "errors" that should really be warnings
abortOnError = false
//Uncomment disable lines for test builds...
//disable 'MissingTranslation'bin
//disable 'ExtraTranslation'
}
defaultConfig {
// TODO If this is ever modified, change application_id in strings.xml
applicationId = "org.yuzu.yuzu_emu"
@@ -54,20 +59,6 @@ android {
targetSdk = 33
versionName = getGitVersion()
// If you want to use autoVersion for the versionCode, create a property in local.properties
// named "autoVersioned" and set it to "true"
val properties = Properties()
val versionProperty = try {
properties.load(project.rootProject.file("local.properties").inputStream())
properties.getProperty("autoVersioned") ?: ""
} catch (e: Exception) { "" }
versionCode = if (versionProperty == "true") {
autoVersion
} else {
1
}
ndk {
@SuppressLint("ChromeOsAbiSupport")
abiFilters += listOf("arm64-v8a")
@@ -82,7 +73,16 @@ android {
// Signed by release key, allowing for upload to Play Store.
release {
resValue("string", "app_name_suffixed", "yuzu")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
}
register("relWithVersionCode") {
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = false
@@ -95,7 +95,6 @@ android {
// builds a release build that doesn't need signing
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
register("relWithDebInfo") {
resValue("string", "app_name_suffixed", "yuzu Debug Release")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = true
@@ -103,19 +102,16 @@ android {
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
versionNameSuffix = "-relWithDebInfo"
applicationIdSuffix = ".relWithDebInfo"
versionNameSuffix = "-debug"
isJniDebuggable = true
}
// Signed by debug key disallowing distribution on Play Store.
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
debug {
resValue("string", "app_name_suffixed", "yuzu Debug")
isDebuggable = true
isJniDebuggable = true
versionNameSuffix = "-debug"
applicationIdSuffix = ".debug"
}
}
@@ -160,42 +156,24 @@ android {
}
}
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
version.set("0.47.1")
android.set(true)
ignoreFailures.set(false)
disabledRules.set(
setOf(
"no-wildcard-imports",
"package-name",
"import-ordering"
)
)
reporters {
reporter(ReporterType.CHECKSTYLE)
}
}
dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.6.0")
implementation("androidx.fragment:fragment-ktx:1.5.7")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.1.0")
implementation("androidx.window:window:1.0.0")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}

View File

@@ -6,10 +6,17 @@ SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-feature
android:name="android.hardware.gamepad"
android:required="false"/>
<uses-feature
android:name="android.hardware.vulkan.version"
android:version="0x401000"
android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -18,14 +25,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
android:label="@string/app_name_suffixed"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:allowBackup="true"
android:hasFragileUserData="true"
android:supportsRtl="true"
android:isGame="true"
android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
android:banner="@drawable/ic_launcher"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
@@ -38,10 +44,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@@ -53,9 +58,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<activity
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop"
android:screenOrientation="userLandscape"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:exported="true">
<intent-filter>

View File

@@ -14,18 +14,16 @@ import android.widget.TextView
import androidx.annotation.Keep
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
import org.yuzu.yuzu_emu.utils.FileUtil.exists
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
import org.yuzu.yuzu_emu.utils.Log.error
import org.yuzu.yuzu_emu.utils.Log.verbose
import org.yuzu.yuzu_emu.utils.Log.warning
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
import java.lang.ref.WeakReference
/**
* Class which contains methods that interact
@@ -76,9 +74,7 @@ object NativeLibrary {
fun openContentUri(path: String?, openmode: String?): Int {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else {
openContentUri(appContext, path, openmode)
}
} else openContentUri(appContext, path, openmode)
}
@Keep
@@ -86,29 +82,7 @@ object NativeLibrary {
fun getSize(path: String?): Long {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path)
} else {
getFileSize(appContext, path)
}
}
@Keep
@JvmStatic
fun exists(path: String?): Boolean {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
exists(appContext, path)
}
}
@Keep
@JvmStatic
fun isDirectory(path: String?): Boolean {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
isDirectory(appContext, path)
}
} else getFileSize(appContext, path)
}
/**
@@ -249,12 +223,8 @@ object NativeLibrary {
external fun getCompany(filename: String): String
external fun isHomebrew(filename: String): Boolean
external fun setAppDirectory(directory: String)
external fun installFileToNand(filename: String): Int
external fun initializeGpuDriver(
hookLibDir: String?,
customDriverDir: String?,
@@ -308,11 +278,6 @@ object NativeLibrary {
*/
external fun isRunning(): Boolean
/**
* Returns true if emulation is paused.
*/
external fun isPaused(): Boolean
/**
* Returns the performance stats for the current game
*/
@@ -462,9 +427,7 @@ object NativeLibrary {
Html.FROM_HTML_MODE_LEGACY
)
)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
emulationActivity.finish()
}
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() }
.setOnDismissListener { emulationActivity.finish() }
emulationActivity.runOnUiThread {
val alert = builder.create()
@@ -542,15 +505,4 @@ object NativeLibrary {
const val RELEASED = 0
const val PRESSED = 1
}
/**
* Result from installFileToNand
*/
object InstallFileToNandResult {
const val Success = 0
const val SuccessFileOverwritten = 1
const val Error = 2
const val ErrorBaseGame = 3
const val ErrorFilenameExtension = 4
}
}

View File

@@ -7,12 +7,12 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import java.io.File
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import java.io.File
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() {
private fun createNotificationChannels() {
@@ -21,9 +21,7 @@ class YuzuApplication : Application() {
getString(R.string.emulation_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
)
emulationChannel.description = getString(
R.string.emulation_notification_channel_description
)
emulationChannel.description = getString(R.string.emulation_notification_channel_description)
emulationChannel.setSound(null, null)
emulationChannel.vibrationPattern = null
@@ -50,7 +48,7 @@ class YuzuApplication : Application() {
GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo()
createNotificationChannels()
createNotificationChannels();
}
companion object {

View File

@@ -4,55 +4,48 @@
package org.yuzu.yuzu_emu.activities
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.graphics.Rect
import android.graphics.drawable.Icon
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.Bundle
import android.util.Rational
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.fragment.NavHostFragment
import kotlin.math.roundToInt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.window.layout.WindowInfoTracker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.fragments.EmulationFragment
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
import org.yuzu.yuzu_emu.utils.ThemeHelper
import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding
private var controllerMappingHelper: ControllerMappingHelper? = null
var isActivityRecreated = false
private var emulationFragment: EmulationFragment? = null
private lateinit var nfcReader: NfcReader
private lateinit var inputHandler: InputHandler
@@ -61,10 +54,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private var motionTimestamp: Long = 0
private var flipMotionOrientation: Boolean = false
private val actionPause = "ACTION_EMULATOR_PAUSE"
private val actionPlay = "ACTION_EMULATOR_PLAY"
private val settingsViewModel: SettingsViewModel by viewModels()
private lateinit var game: Game
override fun onDestroy() {
stopForegroundService(this)
@@ -74,34 +64,48 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
settingsViewModel.settings.loadSettings()
super.onCreate(savedInstanceState)
binding = ActivityEmulationBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
val navController = navHostFragment.navController
navController
.setGraph(R.navigation.emulation_navigation, intent.extras)
isActivityRecreated = savedInstanceState != null
if (savedInstanceState == null) {
// Get params we were passed
game = intent.parcelable(EXTRA_SELECTED_GAME)!!
isActivityRecreated = false
} else {
isActivityRecreated = true
restoreState(savedInstanceState)
}
controllerMappingHelper = ControllerMappingHelper()
// Set these options now so that the SurfaceView the game renders into is the right size.
enableFullscreenImmersive()
setContentView(R.layout.activity_emulation)
window.decorView.setBackgroundColor(getColor(android.R.color.black))
// Find or create the EmulationFragment
emulationFragment =
supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
if (emulationFragment == null) {
emulationFragment = EmulationFragment.newInstance(game)
supportFragmentManager.beginTransaction()
.add(R.id.frame_emulation_fragment, emulationFragment!!)
.commit()
}
title = game.title
nfcReader = NfcReader(this)
nfcReader.initialize()
inputHandler = InputHandler()
inputHandler.initialize()
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@EmulationActivity)
.windowLayoutInfo(this@EmulationActivity)
.collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) }
}
}
// Start a foreground service to prevent the app from getting killed in the background
val startIntent = Intent(this, ForegroundService::class.java)
startForegroundService(startIntent)
@@ -135,7 +139,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader.startScanning()
startMotionSensorListener()
buildPictureInPictureParams()
NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.LayoutOption_MobileLandscape,
Surface.ROTATION_90
)
}
override fun onPause() {
@@ -144,22 +151,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener()
}
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
}
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
nfcReader.onNewIntent(intent)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(EXTRA_SELECTED_GAME, game)
super.onSaveInstanceState(outState)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
@@ -246,6 +248,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
private fun restoreState(savedInstanceState: Bundle) {
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
}
private fun enableFullscreenImmersive() {
WindowCompat.setDecorFitsSystemWindows(window, false)
@@ -256,100 +262,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
PictureInPictureParams.Builder {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
3 -> Rational(16, 10)
else -> null // Best fit
}
return this.apply { aspectRatio?.let { setAspectRatio(it) } }
}
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder():
PictureInPictureParams.Builder {
val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf()
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
if (NativeLibrary.isPaused()) {
val playIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_play)
val playPendingIntent = PendingIntent.getBroadcast(
this@EmulationActivity,
R.drawable.ic_pip_play,
Intent(actionPlay),
pendingFlags
)
val playRemoteAction = RemoteAction(
playIcon,
getString(R.string.play),
getString(R.string.play),
playPendingIntent
)
pictureInPictureActions.add(playRemoteAction)
} else {
val pauseIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_pause)
val pausePendingIntent = PendingIntent.getBroadcast(
this@EmulationActivity,
R.drawable.ic_pip_pause,
Intent(actionPause),
pendingFlags
)
val pauseRemoteAction = RemoteAction(
pauseIcon,
getString(R.string.pause),
getString(R.string.pause),
pausePendingIntent
)
pictureInPictureActions.add(pauseRemoteAction)
}
return this.apply { setActions(pictureInPictureActions) }
}
fun buildPictureInPictureParams() {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pictureInPictureParamsBuilder.setAutoEnterEnabled(
BooleanSetting.PICTURE_IN_PICTURE.boolean
)
}
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
}
private var pictureInPictureReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == actionPlay) {
if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation()
} else if (intent.action == actionPause) {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
}
buildPictureInPictureParams()
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
IntentFilter().apply {
addAction(actionPause)
addAction(actionPlay)
}.also {
registerReceiver(pictureInPictureReceiver, it)
}
} else {
try {
unregisterReceiver(pictureInPictureReceiver)
} catch (ignored: Exception) {
}
}
}
private fun startMotionSensorListener() {
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

View File

@@ -16,7 +16,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
@@ -24,13 +23,13 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import kotlinx.coroutines.launch
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.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.model.GamesViewModel
class GameAdapter(private val activity: AppCompatActivity) :
@@ -59,10 +58,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
override fun onClick(view: View) {
val holder = view.tag as GameViewHolder
val gameExists = DocumentFile.fromSingleUri(
YuzuApplication.appContext,
Uri.parse(holder.game.path)
)?.exists() == true
val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true
if (!gameExists) {
Toast.makeText(
YuzuApplication.appContext,
@@ -82,8 +78,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
)
.apply()
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
view.findNavController().navigate(action)
EmulationActivity.launch(activity, holder.game)
}
inner class GameViewHolder(val binding: CardGameBinding) :

View File

@@ -58,12 +58,11 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
)
when (option.titleId) {
R.string.get_early_access ->
binding.optionLayout.background =
ContextCompat.getDrawable(
binding.optionCard.context,
R.drawable.premium_background
)
R.string.get_early_access -> binding.optionLayout.background =
ContextCompat.getDrawable(
binding.optionCard.context,
R.drawable.premium_background
)
}
}
}

View File

@@ -1,54 +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.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
import org.yuzu.yuzu_emu.model.License
class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
val binding =
ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.setOnClickListener(this)
return LicenseViewHolder(binding)
}
override fun getItemCount(): Int = licenses.size
override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
holder.bind(licenses[position])
}
override fun onClick(view: View) {
val license = (view.tag as LicenseViewHolder).license
LicenseBottomSheetDialogFragment.newInstance(license)
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
}
inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
lateinit var license: License
init {
itemView.tag = this
}
fun bind(license: License) {
this.license = license
val context = YuzuApplication.appContext
binding.textSettingName.text = context.getString(license.titleId)
binding.textSettingDescription.text = context.getString(license.descriptionId)
}
}
}

View File

@@ -12,10 +12,10 @@ import android.view.WindowInsets
import android.view.inputmethod.InputMethodManager
import androidx.annotation.Keep
import androidx.core.view.ViewCompat
import java.io.Serializable
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
import java.io.Serializable
@Keep
object SoftwareKeyboard {
@@ -40,22 +40,19 @@ object SoftwareKeyboard {
// There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
val handler = Handler(Looper.myLooper()!!)
val delayMs = 500
handler.postDelayed(
object : Runnable {
override fun run() {
val insets = ViewCompat.getRootWindowInsets(overlayView)
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
if (isKeyboardVisible) {
handler.postDelayed(this, delayMs.toLong())
return
}
// No longer visible, submit the result.
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
handler.postDelayed(object : Runnable {
override fun run() {
val insets = ViewCompat.getRootWindowInsets(overlayView)
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
if (isKeyboardVisible) {
handler.postDelayed(this, delayMs.toLong())
return
}
},
delayMs.toLong()
)
// No longer visible, submit the result.
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
}
}, delayMs.toLong())
}
@JvmStatic

View File

@@ -63,7 +63,7 @@ class KeyboardDialogFragment : DialogFragment() {
val headerText =
config.header_text!!.ifEmpty { resources.getString(R.string.software_keyboard) }
val okText =
config.ok_text!!.ifEmpty { resources.getString(R.string.submit) }
config.ok_text!!.ifEmpty { resources.getString(android.R.string.ok) }
return MaterialAlertDialogBuilder(requireContext())
.setTitle(headerText)

View File

@@ -20,10 +20,7 @@ object DiskShaderCacheProgress {
emulationActivity.getString(R.string.loading),
emulationActivity.getString(R.string.preparing_shaders)
)
fragment.show(
emulationActivity.supportFragmentManager,
ShaderProgressDialogFragment.TAG
)
fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG)
}
synchronized(finishLock) { finishLock.wait() }
}

View File

@@ -62,9 +62,7 @@ class ShaderProgressDialogFragment : DialogFragment() {
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
alertDialog.setMessage(msg)
}
synchronized(DiskShaderCacheProgress.finishLock) {
DiskShaderCacheProgress.finishLock.notifyAll()
}
synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() }
}
override fun onDestroyView() {

View File

@@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract
import android.provider.DocumentsProvider
import android.webkit.MimeTypeMap
import java.io.*
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.getPublicFilesDir
import java.io.*
class DocumentProvider : DocumentsProvider() {
private val baseDirectory: File
@@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() {
DocumentsContract.Document.COLUMN_SIZE
)
const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user"
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user"
const val ROOT_ID: String = "root"
}
@@ -58,11 +58,7 @@ class DocumentProvider : DocumentsProvider() {
private fun getFile(documentId: String): File {
if (documentId.startsWith(ROOT_ID)) {
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
if (!file.exists()) {
throw FileNotFoundException(
"${file.absolutePath} ($documentId) not found"
)
}
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
return file
} else {
throw FileNotFoundException("'$documentId' is not in any known root")
@@ -84,8 +80,7 @@ class DocumentProvider : DocumentsProvider() {
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
add(
DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
)
add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
@@ -132,13 +127,11 @@ class DocumentProvider : DocumentsProvider() {
try {
if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
if (!newFile.mkdir()) {
if (!newFile.mkdir())
throw IOException("Failed to create directory")
}
} else {
if (!newFile.createNewFile()) {
if (!newFile.createNewFile())
throw IOException("Failed to create file")
}
}
} catch (e: IOException) {
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
@@ -149,9 +142,8 @@ class DocumentProvider : DocumentsProvider() {
override fun deleteDocument(documentId: String?) {
val file = getFile(documentId!!)
if (!file.delete()) {
if (!file.delete())
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
}
override fun removeDocument(documentId: String, parentDocumentId: String?) {
@@ -159,55 +151,38 @@ class DocumentProvider : DocumentsProvider() {
val file = getFile(documentId)
if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
if (!file.delete()) {
if (!file.delete())
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
} else {
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
}
override fun renameDocument(documentId: String?, displayName: String?): String {
if (displayName == null) {
throw FileNotFoundException(
"Couldn't rename document '$documentId' as the new name is null"
)
}
if (displayName == null)
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
val sourceFile = getFile(documentId!!)
val sourceParentFile = sourceFile.parentFile
?: throw FileNotFoundException(
"Couldn't rename document '$documentId' as it has no parent"
)
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
val destFile = sourceParentFile.resolve(displayName)
try {
if (!sourceFile.renameTo(destFile)) {
throw FileNotFoundException(
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'"
)
}
if (!sourceFile.renameTo(destFile))
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
} catch (e: Exception) {
throw FileNotFoundException(
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " +
"${e.message}"
)
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
}
return getDocumentId(destFile)
}
private fun copyDocument(
sourceDocumentId: String,
sourceParentDocumentId: String,
sourceDocumentId: String, sourceParentDocumentId: String,
targetParentDocumentId: String?
): String {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
throw FileNotFoundException(
"Couldn't copy document '$sourceDocumentId' as its parent is not " +
"'$sourceParentDocumentId'"
)
}
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
return copyDocument(sourceDocumentId, targetParentDocumentId)
}
@@ -218,13 +193,8 @@ class DocumentProvider : DocumentsProvider() {
val newFile = parent.resolveWithoutConflict(oldFile.name)
try {
if (!(
newFile.createNewFile() && newFile.setWritable(true) &&
newFile.setReadable(true)
)
) {
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
throw IOException("Couldn't create new file")
}
FileInputStream(oldFile).use { inStream ->
FileOutputStream(newFile).use { outStream ->
@@ -239,14 +209,12 @@ class DocumentProvider : DocumentsProvider() {
}
override fun moveDocument(
sourceDocumentId: String,
sourceParentDocumentId: String?,
sourceDocumentId: String, sourceParentDocumentId: String?,
targetParentDocumentId: String?
): String {
try {
val newDocumentId = copyDocument(
sourceDocumentId,
sourceParentDocumentId!!,
sourceDocumentId, sourceParentDocumentId!!,
targetParentDocumentId
)
removeDocument(sourceDocumentId, sourceParentDocumentId)
@@ -277,30 +245,24 @@ class DocumentProvider : DocumentsProvider() {
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
add(
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
if (localFile == baseDirectory) {
context!!.getString(R.string.app_name)
} else {
localFile.name
}
if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name
)
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
add(DocumentsContract.Document.COLUMN_FLAGS, flags)
if (localFile == baseDirectory) {
if (localFile == baseDirectory)
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
}
}
return cursor
}
private fun getTypeForFile(file: File): Any {
return if (file.isDirectory) {
return if (file.isDirectory)
DocumentsContract.Document.MIME_TYPE_DIR
} else {
else
getTypeForName(file.name)
}
}
private fun getTypeForName(name: String): Any {
@@ -308,9 +270,8 @@ class DocumentProvider : DocumentsProvider() {
if (lastDot >= 0) {
val extension = name.substring(lastDot + 1)
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
if (mime != null) {
if (mime != null)
return mime
}
}
return "application/octect-stream"
}

View File

@@ -8,10 +8,6 @@ enum class BooleanSetting(
override val section: String,
override val defaultValue: Boolean
) : AbstractBooleanSetting {
CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
override var boolean: Boolean = defaultValue
@@ -31,7 +27,6 @@ enum class BooleanSetting(
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
PICTURE_IN_PICTURE,
USE_CUSTOM_RTC
)

View File

@@ -26,18 +26,13 @@ enum class IntSetting(
RENDERER_FORCE_MAX_CLOCK(
"force_max_clock",
Settings.SECTION_RENDERER,
0
1
),
RENDERER_ASYNCHRONOUS_SHADERS(
"use_asynchronous_shaders",
Settings.SECTION_RENDERER,
0
),
RENDERER_REACTIVE_FLUSHING(
"use_reactive_flushing",
Settings.SECTION_RENDERER,
0
),
RENDERER_DEBUG(
"debug",
Settings.SECTION_RENDERER,
@@ -93,11 +88,6 @@ enum class IntSetting(
Settings.SECTION_RENDERER,
0
),
RENDERER_SCREEN_LAYOUT(
"screen_layout",
Settings.SECTION_RENDERER,
Settings.LayoutOption_MobileLandscape
),
RENDERER_ASPECT_RATIO(
"aspect_ratio",
Settings.SECTION_RENDERER,

View File

@@ -4,11 +4,11 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
import java.util.*
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import java.util.*
class Settings {
private var gameId: String? = null
@@ -39,7 +39,7 @@ class Settings {
val isEmpty: Boolean
get() = sections.isEmpty()
fun loadSettings(view: SettingsActivityView? = null) {
fun loadSettings(view: SettingsActivityView) {
sections = SettingsSectionMap()
loadYuzuSettings(view)
if (!TextUtils.isEmpty(gameId)) {
@@ -48,13 +48,13 @@ class Settings {
isLoaded = true
}
private fun loadYuzuSettings(view: SettingsActivityView?) {
private fun loadYuzuSettings(view: SettingsActivityView) {
for ((fileName) in configFileSectionsMap) {
sections.putAll(SettingsFile.readFile(fileName, view))
}
}
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView) {
// Custom game settings
mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
}
@@ -108,7 +108,6 @@ class Settings {
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
const val PREF_OVERLAY_INIT = "OverlayInit"
const val PREF_CONTROL_SCALE = "controlScale"
@@ -133,6 +132,7 @@ class Settings {
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
const val PREF_MENU_SETTINGS_SCREEN_LAYOUT = "EmulationMenuSettings_ScreenLayout"
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
@@ -143,10 +143,6 @@ class Settings {
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
const val LayoutOption_Unspecified = 0
const val LayoutOption_MobilePortrait = 4
const val LayoutOption_MobileLandscape = 5
init {
configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
listOf(

View File

@@ -8,7 +8,6 @@ enum class StringSetting(
override val section: String,
override val defaultValue: String
) : AbstractStringSetting {
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
override var string: String = defaultValue

View File

@@ -3,8 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class HeaderSetting(
titleId: Int
) : SettingsItem(null, titleId, 0) {
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_HEADER
}

View File

@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
class SingleChoiceSetting(
setting: AbstractIntSetting?,

View File

@@ -3,11 +3,13 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.utils.Log
import kotlin.math.roundToInt
class SliderSetting(
setting: AbstractSetting?,
@@ -17,7 +19,7 @@ class SliderSetting(
val max: Int,
val units: String,
val key: String? = null,
val defaultValue: Int? = null
val defaultValue: Int? = null,
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER

View File

@@ -5,25 +5,24 @@ package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
class StringSingleChoiceSetting(
val key: String? = null,
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val choices: Array<String>,
val values: Array<String>?,
val key: String? = null,
val choicesId: Array<String>,
private val valuesId: Array<String>?,
private val defaultValue: String? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE
fun getValueAt(index: Int): String? {
if (values == null) return null
return if (index >= 0 && index < values.size) {
values[index]
} else {
""
}
if (valuesId == null) return null
return if (index >= 0 && index < valuesId.size) {
valuesId[index]
} else ""
}
val selectedValue: String
@@ -36,8 +35,8 @@ class StringSingleChoiceSetting(
val selectValueIndex: Int
get() {
val selectedValue = selectedValue
for (i in values!!.indices) {
if (values[i] == selectedValue) {
for (i in valuesId!!.indices) {
if (valuesId[i] == selectedValue) {
return i
}
}

View File

@@ -3,10 +3,13 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SubmenuSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val menuKey: String
) : SettingsItem(null, titleId, descriptionId) {
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SUBMENU
}

View File

@@ -8,18 +8,17 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.core.view.updatePadding
import com.google.android.material.color.MaterialColors
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@@ -30,6 +29,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.*
import java.io.IOException
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private val presenter = SettingsActivityPresenter(this)
@@ -59,9 +59,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setSupportActionBar(binding.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity(
MaterialColors.getColor(
@@ -77,8 +75,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() = navigateBack()
}
)
})
setInsets()
}
@@ -151,13 +148,11 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private fun areSystemAnimationsEnabled(): Boolean {
val duration = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
1f
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f
)
val transition = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
1f
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f
)
return duration != 0f && transition != 0f
}
@@ -212,9 +207,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.frameContent
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.updatePadding(
@@ -246,17 +239,5 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
settings.putExtra(ARG_GAME_ID, gameId)
context.startActivity(settings)
}
fun launch(
context: Context,
launcher: ActivityResultLauncher<Intent>,
menuTag: String?,
gameId: String?
) {
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
settings.putExtra(ARG_GAME_ID, gameId)
launcher.launch(settings)
}
}
}

View File

@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
import java.io.File
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
val settings: Settings get() = activityView.settings
@@ -46,15 +46,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
private fun prepareDirectoriesIfNeeded() {
val configFile =
File(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
if (!configFile.exists()) {
Log.error(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
Log.error("yuzu config file could not be found!")
}

View File

@@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -138,7 +139,7 @@ class SettingsAdapter(
clickedItem = item
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
.setSingleChoiceItems(item.choicesId, item.selectValueIndex, this)
.show()
}

View File

@@ -50,10 +50,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireActivity())
val dividerDecoration = MaterialDividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
)
val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply {
adapter = settingsAdapter
@@ -102,9 +99,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.listSettings
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(bottom = insets.bottom)
windowInsets

View File

@@ -7,6 +7,7 @@ import android.content.SharedPreferences
import android.os.Build
import android.text.TextUtils
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.features.settings.model.AbstractBooleanSetting
@@ -42,7 +43,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
fun putSetting(setting: AbstractSetting) {
if (setting.section == null || setting.key == null) {
if (setting.section == null) {
return
}
@@ -67,7 +68,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
fragmentView.showToastMessage("Unimplemented menu", false)
return
@@ -78,10 +78,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_advanced_settings))
sl.apply {
add(
SubmenuSetting(
null,
R.string.preferences_general,
0,
Settings.SECTION_GENERAL
@@ -89,6 +90,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SubmenuSetting(
null,
R.string.preferences_system,
0,
Settings.SECTION_SYSTEM
@@ -96,6 +98,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SubmenuSetting(
null,
R.string.preferences_graphics,
0,
Settings.SECTION_RENDERER
@@ -103,18 +106,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SubmenuSetting(
null,
R.string.preferences_audio,
0,
Settings.SECTION_AUDIO
)
)
add(
SubmenuSetting(
R.string.preferences_debug,
0,
Settings.SECTION_DEBUG
)
)
add(
RunnableSetting(
R.string.reset_to_default,
@@ -165,15 +162,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.CPU_ACCURACY.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.PICTURE_IN_PICTURE,
R.string.picture_in_picture,
R.string.picture_in_picture_description,
BooleanSetting.PICTURE_IN_PICTURE.key,
BooleanSetting.PICTURE_IN_PICTURE.defaultValue
)
)
}
}
@@ -235,6 +223,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
sl.apply {
add(
SingleChoiceSetting(
IntSetting.RENDERER_BACKEND,
R.string.renderer_api,
0,
R.array.rendererApiNames,
R.array.rendererApiValues,
IntSetting.RENDERER_BACKEND.key,
IntSetting.RENDERER_BACKEND.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.RENDERER_ACCURACY,
@@ -290,17 +289,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.RENDERER_ANTI_ALIASING.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.RENDERER_SCREEN_LAYOUT,
R.string.renderer_screen_layout,
0,
R.array.rendererScreenLayoutNames,
R.array.rendererScreenLayoutValues,
IntSetting.RENDERER_SCREEN_LAYOUT.key,
IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.RENDERER_ASPECT_RATIO,
@@ -341,11 +329,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.RENDERER_REACTIVE_FLUSHING,
R.string.renderer_reactive_flushing,
R.string.renderer_reactive_flushing_description,
IntSetting.RENDERER_REACTIVE_FLUSHING.key,
IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
IntSetting.RENDERER_DEBUG,
R.string.renderer_debug,
R.string.renderer_debug_description,
IntSetting.RENDERER_DEBUG.key,
IntSetting.RENDERER_DEBUG.defaultValue
)
)
}
@@ -353,31 +341,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply {
add(
StringSingleChoiceSetting(
StringSetting.AUDIO_OUTPUT_ENGINE,
R.string.audio_output_engine,
0,
settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
settingsActivity.resources.getStringArray(R.array.outputEngineValues),
StringSetting.AUDIO_OUTPUT_ENGINE.key,
StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
)
sl.add(
SliderSetting(
IntSetting.AUDIO_VOLUME,
R.string.audio_volume,
R.string.audio_volume_description,
0,
100,
"%",
IntSetting.AUDIO_VOLUME.key,
IntSetting.AUDIO_VOLUME.defaultValue
)
add(
SliderSetting(
IntSetting.AUDIO_VOLUME,
R.string.audio_volume,
R.string.audio_volume_description,
0,
100,
"%",
IntSetting.AUDIO_VOLUME.key,
IntSetting.AUDIO_VOLUME.defaultValue
)
)
}
)
}
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
@@ -476,64 +451,4 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
}
}
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
sl.apply {
add(HeaderSetting(R.string.gpu))
add(
SingleChoiceSetting(
IntSetting.RENDERER_BACKEND,
R.string.renderer_api,
0,
R.array.rendererApiNames,
R.array.rendererApiValues,
IntSetting.RENDERER_BACKEND.key,
IntSetting.RENDERER_BACKEND.defaultValue
)
)
add(
SwitchSetting(
IntSetting.RENDERER_DEBUG,
R.string.renderer_debug,
R.string.renderer_debug_description,
IntSetting.RENDERER_DEBUG.key,
IntSetting.RENDERER_DEBUG.defaultValue
)
)
add(HeaderSetting(R.string.cpu))
add(
SwitchSetting(
BooleanSetting.CPU_DEBUG_MODE,
R.string.cpu_debug_mode,
R.string.cpu_debug_mode_description,
BooleanSetting.CPU_DEBUG_MODE.key,
BooleanSetting.CPU_DEBUG_MODE.defaultValue
)
)
val fastmem = object : AbstractBooleanSetting {
override var boolean: Boolean
get() =
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
set(value) {
BooleanSetting.FASTMEM.boolean = value
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
}
override val key: String? = null
override val section: String = Settings.SECTION_CPU
override val isRuntimeEditable: Boolean = false
override val valueAsString: String = ""
override val defaultValue: Any = true
}
add(
SwitchSetting(
fastmem,
R.string.fastmem,
0
)
)
}
}
}

View File

@@ -4,15 +4,15 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
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 java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
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
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {

View File

@@ -26,14 +26,6 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
for (i in values.indices) {
if (values[i] == item.selectedValue) {
binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
return
}
}
} else if (item is StringSingleChoiceSetting) {
for (i in item.values!!.indices) {
if (item.values[i] == item.selectedValue) {
binding.textSettingDescription.text = item.choices[i]
return
}
}
} else {

View File

@@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import android.widget.CompoundButton
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.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :

View File

@@ -3,8 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.utils
import java.io.*
import java.util.*
import org.ini4j.Wini
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -15,6 +13,8 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.utils.BiMap
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
import java.io.*
import java.util.*
/**
* Contains static methods for interacting with .ini files in which settings are stored.
@@ -37,7 +37,7 @@ object SettingsFile {
private fun readFile(
ini: File?,
isCustomGame: Boolean,
view: SettingsActivityView? = null
view: SettingsActivityView?
): HashMap<String, SettingSection?> {
val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
var reader: BufferedReader? = null
@@ -74,13 +74,10 @@ object SettingsFile {
return sections
}
fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
fun readFile(fileName: String, view: SettingsActivityView): HashMap<String, SettingSection?> {
return readFile(getSettingsFile(fileName), false, view)
}
fun readFile(fileName: String): HashMap<String, SettingSection?> =
readFile(getSettingsFile(fileName), false)
/**
* Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
@@ -91,7 +88,7 @@ object SettingsFile {
*/
fun readCustomGameSettings(
gameId: String,
view: SettingsActivityView?
view: SettingsActivityView
): HashMap<String, SettingSection?> {
return readFile(getCustomGameSettingsFile(gameId), true, view)
}
@@ -137,12 +134,9 @@ object SettingsFile {
for (settingKey in sortedKeySet) {
val setting = settings[settingKey]
NativeLibrary.setUserSetting(
gameId,
mapSectionNameFromIni(
gameId, mapSectionNameFromIni(
section.name
),
setting!!.key,
setting.valueAsString
), setting!!.key, setting.valueAsString
)
}
}
@@ -151,17 +145,13 @@ object SettingsFile {
private fun mapSectionNameFromIni(generalSectionName: String): String? {
return if (sectionsMap.getForward(generalSectionName) != null) {
sectionsMap.getForward(generalSectionName)
} else {
generalSectionName
}
} else generalSectionName
}
private fun mapSectionNameToIni(generalSectionName: String): String {
return if (sectionsMap.getBackward(generalSectionName) != null) {
sectionsMap.getBackward(generalSectionName).toString()
} else {
generalSectionName
}
} else generalSectionName
}
fun getSettingsFile(fileName: String): File {
@@ -244,21 +234,5 @@ object SettingsFile {
val setting = settings[key]
parser.put(header, setting!!.key, setting.valueAsString)
}
BooleanSetting.values().forEach {
if (!keySet.contains(it.key)) {
parser.put(header, it.key, it.valueAsString)
}
}
IntSetting.values().forEach {
if (!keySet.contains(it.key)) {
parser.put(header, it.key, it.valueAsString)
}
}
StringSetting.values().forEach {
if (!keySet.contains(it.key)) {
parser.put(header, it.key, it.valueAsString)
}
}
}
}

View File

@@ -15,12 +15,13 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.core.content.ContextCompat
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.findNavController
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R
@@ -37,7 +38,6 @@ class AboutFragment : Fragment() {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
@@ -54,7 +54,7 @@ class AboutFragment : Fragment() {
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarAbout.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
}
binding.imageLogo.setOnLongClickListener {
@@ -66,15 +66,7 @@ class AboutFragment : Fragment() {
true
}
binding.buttonContributors.setOnClickListener {
openLink(
getString(R.string.contributors_link)
)
}
binding.buttonLicenses.setOnClickListener {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
}
binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) }
binding.textBuildHash.text = BuildConfig.GIT_HASH
binding.buttonBuildHash.setOnClickListener {
@@ -105,9 +97,7 @@ class AboutFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())

View File

@@ -49,11 +49,7 @@ class EarlyAccessFragment : Fragment() {
parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
}
binding.getEarlyAccessButton.setOnClickListener {
openLink(
getString(R.string.play_store_link)
)
}
binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) }
setInsets()
}
@@ -64,9 +60,7 @@ class EarlyAccessFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())

View File

@@ -7,51 +7,44 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Rational
import android.util.TypedValue
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.getSystemService
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity
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.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.overlay.InputOverlay
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.*
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var preferences: SharedPreferences
@@ -62,30 +55,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
val args by navArgs<EmulationFragmentArgs>()
private var isInFoldableLayout = false
private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
private lateinit var game: Game
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is EmulationActivity) {
emulationActivity = context
NativeLibrary.setEmulationActivity(context)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(context)
.windowLayoutInfo(context)
.collect { updateFoldableLayout(context, it) }
}
}
onReturnFromSettings = context.activityResultRegistry.register(
"SettingsResult",
ActivityResultContracts.StartActivityForResult()
) { updateScreenLayout() }
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@@ -100,7 +76,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
emulationState = EmulationState(args.game.path)
game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!!
emulationState = EmulationState(game.path)
}
/**
@@ -124,7 +101,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updateShowFpsOverlay()
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
args.game.title
game.title
binding.inGameMenu.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.menu_pause_emulation -> {
@@ -149,12 +126,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_settings -> {
SettingsActivity.launch(
requireContext(),
onReturnFromSettings,
SettingsFile.FILE_NAME_CONFIG,
""
)
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
true
}
@@ -179,48 +151,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
requireActivity(),
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
} else {
binding.drawerLayout.open()
}
if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
}
}
)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
}
} else {
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
}
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT
} else {
binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE
}
}
if (!binding.surfaceInputOverlay.isInEditMode) {
refreshInputOverlay()
}
}
})
}
override fun onResume() {
@@ -228,9 +161,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start(requireContext())
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
}
@@ -251,6 +181,37 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDetach()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val emulatorLayout = when (newConfig.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> { EmulationMenuSettings.LayoutOption_MobileLandscape }
Configuration.ORIENTATION_PORTRAIT -> { EmulationMenuSettings.LayoutOption_MobilePortrait }
else -> { EmulationMenuSettings.LayoutOption_MobileLandscape }
}
emulationActivity?.let {
var rotation = it.getSystemService<DisplayManager>()!!
.getDisplay(Display.DEFAULT_DISPLAY).rotation
if (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT) {
rotation = when (rotation) {
Surface.ROTATION_0 -> Surface.ROTATION_90
Surface.ROTATION_90 -> Surface.ROTATION_0
Surface.ROTATION_180 -> Surface.ROTATION_270
Surface.ROTATION_270 -> Surface.ROTATION_180
else -> { rotation }
}
}
if (it.isInPictureInPictureMode) {
NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.LayoutOption_MobileLandscape, rotation
)
} else {
NativeLibrary.notifyOrientationChange(emulatorLayout, rotation)
}
}
}
private fun refreshInputOverlay() {
binding.surfaceInputOverlay.refreshControls()
}
@@ -291,71 +252,48 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
@SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() {
emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape ->
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
}
}
private fun updateScreenLayout() {
binding.surfaceEmulation.setAspectRatio(
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
3 -> Rational(16, 10)
4 -> null // Stretch
else -> Rational(16, 9)
emulationActivity?.let {
when (EmulationMenuSettings.screenLayout) {
EmulationMenuSettings.LayoutOption_MobileLandscape -> {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
EmulationMenuSettings.LayoutOption_MobilePortrait -> {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
}
EmulationMenuSettings.LayoutOption_Default -> {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
else -> { it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE }
}
)
emulationActivity?.buildPictureInPictureParams()
updateOrientation()
}
}
private fun updateFoldableLayout(
emulationActivity: EmulationActivity,
newLayoutInfo: WindowLayoutInfo
) {
val isFolding =
(newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
if (it.isSeparating) {
emulationActivity.requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
// Restrict emulation and overlays to the top of the screen
binding.emulationContainer.layoutParams.height = it.bounds.top
binding.overlayContainer.layoutParams.height = it.bounds.top
// Restrict input and menu drawer to the bottom of the screen
binding.inputContainer.layoutParams.height = it.bounds.bottom
binding.inGameMenu.layoutParams.height = it.bounds.bottom
private val Number.toPx get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics).toInt()
isInFoldableLayout = true
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
refreshInputOverlay()
}
fun updateCurrentLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
if (it.isSeparating) {
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
binding.surfaceEmulation.layoutParams.height = it.bounds.top
binding.inGameMenu.layoutParams.height = it.bounds.bottom
binding.overlayContainer.layoutParams.height = it.bounds.bottom - 48.toPx
binding.overlayContainer.updatePadding(0, 0, 0, 24.toPx)
}
it.isSeparating
} ?: false
}
it.isSeparating
} ?: false
if (!isFolding) {
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.surfaceEmulation.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
isInFoldableLayout = false
updateOrientation()
onConfigurationChanged(resources.configuration)
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.overlayContainer.updatePadding(0, 0, 0, 0)
updateScreenLayout()
}
binding.emulationContainer.requestLayout()
binding.inputContainer.requestLayout()
binding.overlayContainer.requestLayout()
binding.surfaceInputOverlay.requestLayout()
binding.inGameMenu.requestLayout()
binding.overlayContainer.requestLayout()
}
override fun surfaceCreated(holder: SurfaceHolder) {
@@ -380,6 +318,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
popup.menu.apply {
findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps
findItem(R.id.menu_screen_layout).subMenu?.let { subMenu ->
when (EmulationMenuSettings.screenLayout) {
EmulationMenuSettings.LayoutOption_MobileLandscape -> {
subMenu.findItem(R.id.menu_screen_layout_landscape).isChecked = true
}
EmulationMenuSettings.LayoutOption_MobilePortrait -> {
subMenu.findItem(R.id.menu_screen_layout_portrait).isChecked = true
}
EmulationMenuSettings.LayoutOption_Default -> {
subMenu.findItem(R.id.menu_screen_layout_auto).isChecked = true
}
else -> { subMenu.findItem(R.id.menu_screen_layout_landscape).isChecked = true }
}
}
findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter
findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
@@ -395,6 +347,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
R.id.menu_screen_layout_landscape -> {
EmulationMenuSettings.screenLayout = EmulationMenuSettings.LayoutOption_MobileLandscape
updateScreenLayout()
false
}
R.id.menu_screen_layout_portrait -> {
EmulationMenuSettings.screenLayout = EmulationMenuSettings.LayoutOption_MobilePortrait
updateScreenLayout()
false
}
R.id.menu_screen_layout_auto -> {
EmulationMenuSettings.screenLayout = EmulationMenuSettings.LayoutOption_Default
updateScreenLayout()
false
}
R.id.menu_screen_layout -> {
it.subMenu?.setGroupCheckable(R.id.menu_screen_layout_group, true, true)
true
}
R.id.menu_edit_overlay -> {
binding.drawerLayout.close()
binding.surfaceInputOverlay.requestFocus()
@@ -428,7 +403,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.setPositiveButton(android.R.string.ok) { _, _ ->
refreshInputOverlay()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
.show()
@@ -485,19 +459,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
popup.show()
}
@SuppressLint("SourceLockedOrientationActivity")
private fun startConfiguringControls() {
// Lock the current orientation to prevent editing inconsistencies
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} else {
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
}
}
binding.doneControlConfig.visibility = View.VISIBLE
binding.surfaceInputOverlay.setIsInEditMode(true)
}
@@ -505,12 +467,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun stopConfiguringControls() {
binding.doneControlConfig.visibility = View.GONE
binding.surfaceInputOverlay.setIsInEditMode(false)
// Unlock the orientation if it was locked for editing
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
@SuppressLint("SetTextI18n")
@@ -520,22 +476,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
inputScaleSlider.apply {
valueTo = 150F
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt())
}
)
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt())
})
}
inputOpacitySlider.apply {
valueTo = 100F
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt())
}
)
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt())
})
}
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
@@ -567,9 +519,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.inGameMenu
) { v: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
var left = 0
var right = 0
@@ -689,12 +639,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
state = State.PAUSED
}
State.PAUSED -> Log.warning(
"[EmulationFragment] Surface cleared while emulation paused."
)
else -> Log.warning(
"[EmulationFragment] Surface cleared while emulation stopped."
)
State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.")
else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.")
}
}
}
@@ -729,5 +675,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
companion object {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
fun newInstance(game: Game): EmulationFragment {
val args = Bundle()
args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game)
val fragment = EmulationFragment()
fragment.arguments = args
return fragment
}
}
}

View File

@@ -19,10 +19,10 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
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.navigation.fragment.findNavController
@@ -40,7 +40,6 @@ import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
class HomeSettingsFragment : Fragment() {
@@ -68,109 +67,57 @@ class HomeSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
add(
HomeSetting(
R.string.advanced_settings,
R.string.settings_description,
R.drawable.ic_settings
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
)
add(
HomeSetting(
R.string.open_user_folder,
R.string.open_user_folder_description,
R.drawable.ic_folder_open
) { openFileManager() }
)
add(
HomeSetting(
R.string.preferences_theme,
R.string.theme_and_color_description,
R.drawable.ic_palette
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
)
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
HomeSetting(
R.string.install_gpu_driver,
R.string.install_gpu_driver_description,
R.drawable.ic_exit
) { driverInstaller() }
)
val optionsList: MutableList<HomeSetting> = mutableListOf(
HomeSetting(
R.string.advanced_settings,
R.string.settings_description,
R.drawable.ic_settings
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
HomeSetting(
R.string.open_user_folder,
R.string.open_user_folder_description,
R.drawable.ic_folder_open
) { openFileManager() },
HomeSetting(
R.string.preferences_theme,
R.string.theme_and_color_description,
R.drawable.ic_palette
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
HomeSetting(
R.string.install_gpu_driver,
R.string.install_gpu_driver_description,
R.drawable.ic_exit
) { driverInstaller() },
HomeSetting(
R.string.install_amiibo_keys,
R.string.install_amiibo_keys_description,
R.drawable.ic_nfc
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
HomeSetting(
R.string.select_games_folder,
R.string.select_games_folder_description,
R.drawable.ic_add
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
HomeSetting(
R.string.manage_save_data,
R.string.import_export_saves_description,
R.drawable.ic_save
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
HomeSetting(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
R.drawable.ic_unlock
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
HomeSetting(
R.string.about,
R.string.about_description,
R.drawable.ic_info_outline
) {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
parentFragmentManager.primaryNavigationFragment?.findNavController()
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
}
add(
HomeSetting(
R.string.install_amiibo_keys,
R.string.install_amiibo_keys_description,
R.drawable.ic_nfc
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
)
add(
HomeSetting(
R.string.install_game_content,
R.string.install_game_content_description,
R.drawable.ic_system_update_alt
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
)
add(
HomeSetting(
R.string.select_games_folder,
R.string.select_games_folder_description,
R.drawable.ic_add
) {
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
}
)
add(
HomeSetting(
R.string.manage_save_data,
R.string.import_export_saves_description,
R.drawable.ic_save
) {
ImportExportSavesFragment().show(
parentFragmentManager,
ImportExportSavesFragment.TAG
)
}
)
add(
HomeSetting(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
R.drawable.ic_unlock
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
)
add(
HomeSetting(
R.string.install_firmware,
R.string.install_firmware_description,
R.drawable.ic_firmware
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
)
add(
HomeSetting(
R.string.share_log,
R.string.share_log_description,
R.drawable.ic_log
) { shareLog() }
)
add(
HomeSetting(
R.string.about,
R.string.about_description,
R.drawable.ic_info_outline
) {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
parentFragmentManager.primaryNavigationFragment?.findNavController()
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
}
)
}
)
if (!BuildConfig.PREMIUM) {
optionsList.add(
@@ -257,11 +204,7 @@ class HomeSettingsFragment : Fragment() {
val intent = Intent(action)
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
intent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
return intent
}
@@ -319,33 +262,8 @@ class HomeSettingsFragment : Fragment() {
.show()
}
private fun shareLog() {
val file = DocumentFile.fromSingleUri(
mainActivity,
DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/log/yuzu_log.txt"
)
)!!
if (file.exists()) {
val intent = Intent(Intent.ACTION_SEND)
.setDataAndType(file.uri, FileUtil.TEXT_PLAIN)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, file.uri)
startActivity(Intent.createChooser(intent, getText(R.string.share_log)))
} else {
Toast.makeText(
requireContext(),
getText(R.string.share_log_missing),
Toast.LENGTH_SHORT
).show()
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)

View File

@@ -15,14 +15,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.FilenameFilter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -31,7 +23,18 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.getPublicFilesDir
import org.yuzu.yuzu_emu.utils.FileUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.FilenameFilter
import java.io.IOException
import java.io.InputStream
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class ImportExportSavesFragment : DialogFragment() {
private val context = YuzuApplication.appContext
@@ -98,7 +101,7 @@ class ImportExportSavesFragment : DialogFragment() {
val outputZipFile = File(
tempFolder,
"yuzu saves - ${
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
}.zip"
)
outputZipFile.createNewFile()
@@ -106,14 +109,12 @@ class ImportExportSavesFragment : DialogFragment() {
saveFolder.walkTopDown().forEach { file ->
val zipFileName =
file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
if (zipFileName == "") {
if (zipFileName == "")
return@forEach
}
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
zos.putNextEntry(entry)
if (file.isFile) {
if (file.isFile)
file.inputStream().use { fis -> fis.copyTo(zos) }
}
}
}
lastZipCreated = outputZipFile
@@ -123,6 +124,33 @@ class ImportExportSavesFragment : DialogFragment() {
return true
}
/**
* Extracts the save files located in the given zip file and copies them to the saves folder.
* @exception IOException if the file was being created outside of the target directory
*/
private fun unzip(zipStream: InputStream, destDir: File): Boolean {
val zis = ZipInputStream(BufferedInputStream(zipStream))
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
val entryName = entry.name
val entryFile = File(destDir, entryName)
if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
zis.close()
throw IOException("Entry is outside of the target dir: " + entryFile.name)
}
if (entry.isDirectory) {
entryFile.mkdirs()
} else {
entryFile.parentFile?.mkdirs()
entryFile.createNewFile()
entryFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
}
zis.close()
return true
}
/**
* Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
*/
@@ -139,8 +167,7 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) {
val file = DocumentFile.fromSingleUri(
context,
DocumentsContract.buildDocumentUri(
context, DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
)
@@ -177,7 +204,7 @@ class ImportExportSavesFragment : DialogFragment() {
try {
CoroutineScope(Dispatchers.IO).launch {
FileUtil.unzip(inputZip, cacheSaveDir)
unzip(inputZip, cacheSaveDir)
cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
File(savesFolder, savePath).deleteRecursively()
File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)

View File

@@ -1,69 +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.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
progressBinding.progressBar.isIndeterminate = true
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(titleId)
.setView(progressBinding.root)
.create()
dialog.setCanceledOnTouchOutside(false)
taskViewModel.isComplete.observe(this) { complete ->
if (complete) {
dialog.dismiss()
when (val result = taskViewModel.result.value) {
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
is MessageDialogFragment -> result.show(
parentFragmentManager,
MessageDialogFragment.TAG
)
}
taskViewModel.clear()
}
}
if (taskViewModel.isRunning.value == false) {
taskViewModel.runTask()
}
return dialog
}
companion object {
const val TAG = "IndeterminateProgressDialogFragment"
private const val TITLE = "Title"
fun newInstance(
activity: AppCompatActivity,
titleId: Int,
task: () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)
dialog.arguments = args
return dialog
}
}
}

View File

@@ -1,59 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.yuzu.yuzu_emu.databinding.DialogLicenseBinding
import org.yuzu.yuzu_emu.model.License
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class LicenseBottomSheetDialogFragment : BottomSheetDialogFragment() {
private var _binding: DialogLicenseBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogLicenseBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
BottomSheetBehavior.from<View>(view.parent as View).state =
BottomSheetBehavior.STATE_HALF_EXPANDED
val license = requireArguments().parcelable<License>(LICENSE)!!
binding.apply {
textTitle.setText(license.titleId)
textLink.setText(license.linkId)
textCopyright.setText(license.copyrightId)
textLicense.setText(license.licenseId)
}
}
companion object {
const val TAG = "LicenseBottomSheetDialogFragment"
const val LICENSE = "License"
fun newInstance(
license: License
): LicenseBottomSheetDialogFragment {
val dialog = LicenseBottomSheetDialogFragment()
val bundle = Bundle()
bundle.putParcelable(LICENSE, license)
dialog.arguments = bundle
return dialog
}
}
}

View File

@@ -1,139 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AppCompatActivity
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.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.LicenseAdapter
import org.yuzu.yuzu_emu.databinding.FragmentLicensesBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.License
class LicensesFragment : Fragment() {
private var _binding: FragmentLicensesBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLicensesBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarLicenses.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
val licenses = listOf(
License(
R.string.license_fidelityfx_fsr,
R.string.license_fidelityfx_fsr_description,
R.string.license_fidelityfx_fsr_link,
R.string.license_fidelityfx_fsr_copyright,
R.string.license_fidelityfx_fsr_text
),
License(
R.string.license_cubeb,
R.string.license_cubeb_description,
R.string.license_cubeb_link,
R.string.license_cubeb_copyright,
R.string.license_cubeb_text
),
License(
R.string.license_dynarmic,
R.string.license_dynarmic_description,
R.string.license_dynarmic_link,
R.string.license_dynarmic_copyright,
R.string.license_dynarmic_text
),
License(
R.string.license_ffmpeg,
R.string.license_ffmpeg_description,
R.string.license_ffmpeg_link,
R.string.license_ffmpeg_copyright,
R.string.license_ffmpeg_text
),
License(
R.string.license_opus,
R.string.license_opus_description,
R.string.license_opus_link,
R.string.license_opus_copyright,
R.string.license_opus_text
),
License(
R.string.license_sirit,
R.string.license_sirit_description,
R.string.license_sirit_link,
R.string.license_sirit_copyright,
R.string.license_sirit_text
),
License(
R.string.license_adreno_tools,
R.string.license_adreno_tools_description,
R.string.license_adreno_tools_link,
R.string.license_adreno_tools_copyright,
R.string.license_adreno_tools_text
)
)
binding.listLicenses.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = LicenseAdapter(requireActivity() as AppCompatActivity, licenses)
}
setInsets()
}
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 mlpAppBar = binding.appbarLicenses.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.appbarLicenses.layoutParams = mlpAppBar
val mlpScrollAbout = binding.listLicenses.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.listLicenses.layoutParams = mlpScrollAbout
binding.listLicenses.updatePadding(bottom = barInsets.bottom)
windowInsets
}
}

View File

@@ -20,7 +20,6 @@ import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.Locale
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.GameAdapter
@@ -30,6 +29,8 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import java.util.Locale
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
@@ -126,18 +127,24 @@ class SearchFragment : Fragment() {
}
}
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_homebrew -> {
baseList.filter {
Log.error("Guh - ${it.path}")
FileUtil.hasExtension(it.path, "nro")
|| FileUtil.hasExtension(it.path, "nso")
}
}
R.id.chip_retail -> baseList.filter {
FileUtil.hasExtension(it.path, "xci") ||
FileUtil.hasExtension(it.path, "nsp")
FileUtil.hasExtension(it.path, "xci")
|| FileUtil.hasExtension(it.path, "nsp")
}
else -> baseList
}
if (binding.searchText.text.toString().isEmpty() &&
binding.chipGroup.checkedChipId != View.NO_ID
if (binding.searchText.text.toString().isEmpty()
&& binding.chipGroup.checkedChipId != View.NO_ID
) {
gamesViewModel.setSearchedGames(filteredList)
return
@@ -172,16 +179,14 @@ class SearchFragment : Fragment() {
private fun focusSearch() {
if (_binding != null) {
binding.searchText.requestFocus()
val imm = requireActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
val imm =
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)

View File

@@ -25,7 +25,6 @@ import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough
import java.io.File
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.SetupAdapter
@@ -36,6 +35,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper
import java.io.File
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
@@ -82,8 +82,7 @@ class SetupFragment : Fragment() {
requireActivity().finish()
}
}
}
)
})
requireActivity().window.navigationBarColor =
ContextCompat.getColor(requireContext(), android.R.color.transparent)
@@ -149,20 +148,14 @@ class SetupFragment : Fragment() {
R.drawable.ic_add,
true,
R.string.add_games,
{
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
},
{ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
true,
R.string.add_games_warning,
R.string.add_games_warning_description,
R.string.add_games_warning_help,
{
val preferences =
PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
}
)
@@ -267,9 +260,7 @@ class SetupFragment : Fragment() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (!it &&
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
) {
if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
PermissionDeniedDialogFragment().show(
childFragmentManager,
PermissionDeniedDialogFragment.TAG
@@ -324,9 +315,7 @@ class SetupFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.setPadding(

View File

@@ -44,9 +44,7 @@ class AutofitGridLayoutManager(
override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
val width = width
val height = height
if (columnWidth > 0 && width > 0 && height > 0 &&
(isColumnWidthChanged || lastWidth != width || lastHeight != height)
) {
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
val totalSpace: Int = if (orientation == VERTICAL) {
width - paddingRight - paddingLeft
} else {

View File

@@ -4,9 +4,9 @@
package org.yuzu.yuzu_emu.model
import android.os.Parcelable
import java.util.HashSet
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import java.util.HashSet
@Parcelize
@Serializable
@@ -16,29 +16,21 @@ class Game(
val regions: String,
val path: String,
val gameId: String,
val company: String,
val isHomebrew: Boolean
val company: String
) : Parcelable {
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
override fun equals(other: Any?): Boolean {
if (other !is Game) {
if (other !is Game)
return false
}
return hashCode() == other.hashCode()
}
override fun hashCode(): Int {
var result = title.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode()
result = 31 * result + gameId.hashCode()
result = 31 * result + company.hashCode()
result = 31 * result + isHomebrew.hashCode()
return result
return title == other.title
&& description == other.description
&& regions == other.regions
&& path == other.path
&& gameId == other.gameId
&& company == other.company
}
companion object {

View File

@@ -10,19 +10,16 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList())
val games: LiveData<List<Game>> get() = _games
@@ -52,13 +49,7 @@ class GamesViewModel : ViewModel() {
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
val game: Game
try {
game = Json.decodeFromString(it)
} catch (e: MissingFieldException) {
return@forEach
}
val game: Game = Json.decodeFromString(it)
val gameExists =
DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
?.exists()
@@ -99,9 +90,8 @@ class GamesViewModel : ViewModel() {
}
fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value == true) {
if (isReloading.value == true)
return
}
_isReloading.postValue(true)
viewModelScope.launch {

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class License(
val titleId: Int,
val descriptionId: Int,
val linkId: Int,
val copyrightId: Int,
val licenseId: Int
) : Parcelable

View File

@@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
private val _result = MutableLiveData<Any>()
val result: LiveData<Any> = _result
private val _isComplete = MutableLiveData<Boolean>()
val isComplete: LiveData<Boolean> = _isComplete
private val _isRunning = MutableLiveData<Boolean>()
val isRunning: LiveData<Boolean> = _isRunning
lateinit var task: () -> Any
init {
clear()
}
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
}
fun runTask() {
if (_isRunning.value == true) {
return
}
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
val res = task()
_result.postValue(res)
_isComplete.postValue(true)
}
}
}

View File

@@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.overlay
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Point
@@ -23,8 +24,6 @@ import android.view.WindowInsets
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.window.layout.WindowMetricsCalculator
import kotlin.math.max
import kotlin.math.min
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
import org.yuzu.yuzu_emu.NativeLibrary.StickType
@@ -32,13 +31,14 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import kotlin.math.max
import kotlin.math.min
/**
* Draws the interactive input overlay on top of the
* [SurfaceView] that is rendering emulation.
*/
class InputOverlay(context: Context, attrs: AttributeSet?) :
SurfaceView(context, attrs),
class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs),
OnTouchListener {
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@@ -51,14 +51,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private lateinit var windowInsets: WindowInsets
var orientation = LANDSCAPE
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
windowInsets = rootWindowInsets
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
defaultOverlay()
}
@@ -95,11 +93,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
var shouldUpdateView = false
val playerIndex =
if (NativeLibrary.isHandheldOnly()) {
NativeLibrary.ConsoleDevice
} else {
NativeLibrary.Player1Device
}
if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
for (button in overlayButtons) {
if (!button.updateStatus(event)) {
@@ -162,9 +156,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
shouldUpdateView = true
}
if (shouldUpdateView) {
if (shouldUpdateView)
invalidate()
}
if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
return true
@@ -240,6 +233,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
// TODO: Provide support for portrait layout
//val orientation =
// if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
for (button in overlayButtons) {
// Determine the button state to apply based on the MotionEvent action flag.
when (event.action and MotionEvent.ACTION_MASK) {
@@ -248,9 +245,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// If no button is being moved now, remember the currently touched button to move.
if (buttonBeingConfigured == null &&
button.bounds.contains(
fingerPositionX,
fingerPositionY
)
fingerPositionX,
fingerPositionY
)
) {
buttonBeingConfigured = button
buttonBeingConfigured!!.onConfigureTouch(event)
@@ -269,7 +266,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
buttonBeingConfigured!!.buttonId,
buttonBeingConfigured!!.bounds.centerX(),
buttonBeingConfigured!!.bounds.centerY(),
orientation
""
)
buttonBeingConfigured = null
}
@@ -302,7 +299,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
dpadBeingConfigured!!.upId,
dpadBeingConfigured!!.bounds.centerX(),
dpadBeingConfigured!!.bounds.centerY(),
orientation
""
)
dpadBeingConfigured = null
}
@@ -314,9 +311,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
joystick.bounds.contains(
fingerPositionX,
fingerPositionY
)
fingerPositionX,
fingerPositionY
)
) {
joystickBeingConfigured = joystick
joystickBeingConfigured!!.onConfigureTouch(event)
@@ -333,7 +330,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
joystickBeingConfigured!!.buttonId,
joystickBeingConfigured!!.bounds.centerX(),
joystickBeingConfigured!!.bounds.centerY(),
orientation
""
)
joystickBeingConfigured = null
}
@@ -536,6 +533,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
overlayButtons.clear()
overlayDpads.clear()
overlayJoysticks.clear()
val orientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
// Add all the enabled overlay items back to the HashSet.
if (EmulationMenuSettings.showOverlay) {
@@ -549,8 +548,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val min = windowSize.first
val max = windowSize.second
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x)
.putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y)
.putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
.putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
.apply()
}
@@ -559,250 +558,145 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
private fun defaultOverlay() {
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
defaultOverlayByLayout(orientation)
if (!preferences.getBoolean(Settings.PREF_OVERLAY_INIT, false)) {
defaultOverlayLandscape()
}
resetButtonPlacement()
preferences.edit()
.putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true)
.putBoolean(Settings.PREF_OVERLAY_INIT, true)
.apply()
}
fun resetButtonPlacement() {
defaultOverlayByLayout(orientation)
defaultOverlayLandscape()
refreshControls()
}
private val landscapeResources = arrayOf(
R.integer.SWITCH_BUTTON_A_X,
R.integer.SWITCH_BUTTON_A_Y,
R.integer.SWITCH_BUTTON_B_X,
R.integer.SWITCH_BUTTON_B_Y,
R.integer.SWITCH_BUTTON_X_X,
R.integer.SWITCH_BUTTON_X_Y,
R.integer.SWITCH_BUTTON_Y_X,
R.integer.SWITCH_BUTTON_Y_Y,
R.integer.SWITCH_TRIGGER_ZL_X,
R.integer.SWITCH_TRIGGER_ZL_Y,
R.integer.SWITCH_TRIGGER_ZR_X,
R.integer.SWITCH_TRIGGER_ZR_Y,
R.integer.SWITCH_BUTTON_DPAD_X,
R.integer.SWITCH_BUTTON_DPAD_Y,
R.integer.SWITCH_TRIGGER_L_X,
R.integer.SWITCH_TRIGGER_L_Y,
R.integer.SWITCH_TRIGGER_R_X,
R.integer.SWITCH_TRIGGER_R_Y,
R.integer.SWITCH_BUTTON_PLUS_X,
R.integer.SWITCH_BUTTON_PLUS_Y,
R.integer.SWITCH_BUTTON_MINUS_X,
R.integer.SWITCH_BUTTON_MINUS_Y,
R.integer.SWITCH_BUTTON_HOME_X,
R.integer.SWITCH_BUTTON_HOME_Y,
R.integer.SWITCH_BUTTON_CAPTURE_X,
R.integer.SWITCH_BUTTON_CAPTURE_Y,
R.integer.SWITCH_STICK_R_X,
R.integer.SWITCH_STICK_R_Y,
R.integer.SWITCH_STICK_L_X,
R.integer.SWITCH_STICK_L_Y
)
private val portraitResources = arrayOf(
R.integer.SWITCH_BUTTON_A_X_PORTRAIT,
R.integer.SWITCH_BUTTON_A_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_B_X_PORTRAIT,
R.integer.SWITCH_BUTTON_B_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_X_X_PORTRAIT,
R.integer.SWITCH_BUTTON_X_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_Y_X_PORTRAIT,
R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT,
R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT,
R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT,
R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT,
R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT,
R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT,
R.integer.SWITCH_TRIGGER_L_X_PORTRAIT,
R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT,
R.integer.SWITCH_TRIGGER_R_X_PORTRAIT,
R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT,
R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT,
R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT,
R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT,
R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT,
R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT,
R.integer.SWITCH_STICK_R_X_PORTRAIT,
R.integer.SWITCH_STICK_R_Y_PORTRAIT,
R.integer.SWITCH_STICK_L_X_PORTRAIT,
R.integer.SWITCH_STICK_L_Y_PORTRAIT
)
private val foldableResources = arrayOf(
R.integer.SWITCH_BUTTON_A_X_FOLDABLE,
R.integer.SWITCH_BUTTON_A_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_B_X_FOLDABLE,
R.integer.SWITCH_BUTTON_B_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_X_X_FOLDABLE,
R.integer.SWITCH_BUTTON_X_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_Y_X_FOLDABLE,
R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE,
R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE,
R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE,
R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE,
R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE,
R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE,
R.integer.SWITCH_TRIGGER_L_X_FOLDABLE,
R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE,
R.integer.SWITCH_TRIGGER_R_X_FOLDABLE,
R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE,
R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE,
R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE,
R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE,
R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE,
R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE,
R.integer.SWITCH_STICK_R_X_FOLDABLE,
R.integer.SWITCH_STICK_R_Y_FOLDABLE,
R.integer.SWITCH_STICK_L_X_FOLDABLE,
R.integer.SWITCH_STICK_L_Y_FOLDABLE
)
private fun getResourceValue(orientation: String, position: Int): Float {
return when (orientation) {
PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
}
}
private fun defaultOverlayByLayout(orientation: String) {
private fun defaultOverlayLandscape() {
// Each value represents the position of the button in relation to the screen size without insets.
preferences.edit()
.putFloat(
ButtonType.BUTTON_A.toString() + "-X$orientation",
getResourceValue(orientation, 0)
ButtonType.BUTTON_A.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_A.toString() + "-Y$orientation",
getResourceValue(orientation, 1)
ButtonType.BUTTON_A.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_B.toString() + "-X$orientation",
getResourceValue(orientation, 2)
ButtonType.BUTTON_B.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_B.toString() + "-Y$orientation",
getResourceValue(orientation, 3)
ButtonType.BUTTON_B.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_X.toString() + "-X$orientation",
getResourceValue(orientation, 4)
ButtonType.BUTTON_X.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_X.toString() + "-Y$orientation",
getResourceValue(orientation, 5)
ButtonType.BUTTON_X.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_Y.toString() + "-X$orientation",
getResourceValue(orientation, 6)
ButtonType.BUTTON_Y.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_Y.toString() + "-Y$orientation",
getResourceValue(orientation, 7)
ButtonType.BUTTON_Y.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
getResourceValue(orientation, 8)
ButtonType.TRIGGER_ZL.toString() + "-X",
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
getResourceValue(orientation, 9)
ButtonType.TRIGGER_ZL.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
getResourceValue(orientation, 10)
ButtonType.TRIGGER_ZR.toString() + "-X",
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
getResourceValue(orientation, 11)
ButtonType.TRIGGER_ZR.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000
)
.putFloat(
ButtonType.DPAD_UP.toString() + "-X$orientation",
getResourceValue(orientation, 12)
ButtonType.DPAD_UP.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000
)
.putFloat(
ButtonType.DPAD_UP.toString() + "-Y$orientation",
getResourceValue(orientation, 13)
ButtonType.DPAD_UP.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_L.toString() + "-X$orientation",
getResourceValue(orientation, 14)
ButtonType.TRIGGER_L.toString() + "-X",
resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_L.toString() + "-Y$orientation",
getResourceValue(orientation, 15)
ButtonType.TRIGGER_L.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_R.toString() + "-X$orientation",
getResourceValue(orientation, 16)
ButtonType.TRIGGER_R.toString() + "-X",
resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000
)
.putFloat(
ButtonType.TRIGGER_R.toString() + "-Y$orientation",
getResourceValue(orientation, 17)
ButtonType.TRIGGER_R.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
getResourceValue(orientation, 18)
ButtonType.BUTTON_PLUS.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
getResourceValue(orientation, 19)
ButtonType.BUTTON_PLUS.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
getResourceValue(orientation, 20)
ButtonType.BUTTON_MINUS.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
getResourceValue(orientation, 21)
ButtonType.BUTTON_MINUS.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_HOME.toString() + "-X$orientation",
getResourceValue(orientation, 22)
ButtonType.BUTTON_HOME.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
getResourceValue(orientation, 23)
ButtonType.BUTTON_HOME.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
getResourceValue(orientation, 24)
ButtonType.BUTTON_CAPTURE.toString() + "-X",
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
.toFloat() / 1000
)
.putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
getResourceValue(orientation, 25)
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
.toFloat() / 1000
)
.putFloat(
ButtonType.STICK_R.toString() + "-X$orientation",
getResourceValue(orientation, 26)
ButtonType.STICK_R.toString() + "-X",
resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000
)
.putFloat(
ButtonType.STICK_R.toString() + "-Y$orientation",
getResourceValue(orientation, 27)
ButtonType.STICK_R.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000
)
.putFloat(
ButtonType.STICK_L.toString() + "-X$orientation",
getResourceValue(orientation, 28)
ButtonType.STICK_L.toString() + "-X",
resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000
)
.putFloat(
ButtonType.STICK_L.toString() + "-Y$orientation",
getResourceValue(orientation, 29)
ButtonType.STICK_L.toString() + "-Y",
resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000
)
.apply()
}
@@ -815,17 +709,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
const val LANDSCAPE = ""
const val PORTRAIT = "_Portrait"
const val FOLDABLE = "_Foldable"
/**
* Resizes a [Bitmap] by a given scale factor
*
* @param context Context for getting the vector drawable
* @param drawableId The ID of the drawable to scale.
* @param scale The scale factor for the bitmap.
* @return The scaled [Bitmap]
* @return The scaled [Bitmap]
*/
private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
@@ -859,13 +749,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
* Gets the safe screen size for drawing the overlay
*
* @param context Context for getting the window metrics
* @return A pair of points, the first being the top left corner of the safe area,
* @return A pair of points, the first being the top left corner of the safe area,
* the second being the bottom right corner of the safe area
*/
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
// Get screen size
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(context as Activity)
val windowMetrics =
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(context as Activity)
var maxY = windowMetrics.bounds.height().toFloat()
var maxX = windowMetrics.bounds.width().toFloat()
var minY = 0
@@ -874,26 +765,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
var cutoutBottom = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null) {
if (insets.boundingRectTop.bottom != 0 &&
insets.boundingRectTop.bottom > maxY / 2
) {
maxY = insets.boundingRectTop.bottom.toFloat()
}
if (insets.boundingRectRight.left != 0 &&
insets.boundingRectRight.left > maxX / 2
) {
maxX = insets.boundingRectRight.left.toFloat()
}
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
insets.boundingRectTop.bottom.toFloat() else maxY
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
insets.boundingRectRight.left.toFloat() else maxX
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
}
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
}
// This makes sure that if we have an inset on one side of the screen, we mirror it on
@@ -993,8 +876,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
val xKey = "$buttonId-X$orientation"
val yKey = "$buttonId-Y$orientation"
val xKey = "$buttonId$orientation-X"
val yKey = "$buttonId$orientation-Y"
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
val drawableX = (drawableXPercent * max.x + min.x).toInt()
@@ -1074,8 +957,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
// These were set in the input overlay configuration menu.
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f)
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f)
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f)
val drawableX = (drawableXPercent * max.x + min.x).toInt()
val drawableY = (drawableYPercent * max.y + min.y).toInt()
val width = overlayDrawable.width
@@ -1141,8 +1024,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f)
val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f)
val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f)
val drawableX = (drawableXPercent * max.x + min.x).toInt()
val drawableY = (drawableYPercent * max.y + min.y).toInt()
val outerScale = 1.66f

View File

@@ -133,10 +133,7 @@ class InputOverlayDrawableDpad(
downButtonState = axisY > VIRT_AXIS_DEADZONE
leftButtonState = axisX < -VIRT_AXIS_DEADZONE
rightButtonState = axisX > VIRT_AXIS_DEADZONE
return oldUpState != upButtonState ||
oldDownState != downButtonState ||
oldLeftState != leftButtonState ||
oldRightState != rightButtonState
return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState
}
return false
}

View File

@@ -9,12 +9,12 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
/**
* Custom [BitmapDrawable] that is capable
@@ -241,22 +241,14 @@ class InputOverlayDrawableJoystick(
private fun setInnerBounds() {
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
if (x > virtBounds.centerX() + virtBounds.width() / 2) {
x =
virtBounds.centerX() + virtBounds.width() / 2
}
if (x < virtBounds.centerX() - virtBounds.width() / 2) {
x =
virtBounds.centerX() - virtBounds.width() / 2
}
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
y =
virtBounds.centerY() + virtBounds.height() / 2
}
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
y =
virtBounds.centerY() - virtBounds.height() / 2
}
if (x > virtBounds.centerX() + virtBounds.width() / 2) x =
virtBounds.centerX() + virtBounds.width() / 2
if (x < virtBounds.centerX() - virtBounds.width() / 2) x =
virtBounds.centerX() - virtBounds.width() / 2
if (y > virtBounds.centerY() + virtBounds.height() / 2) y =
virtBounds.centerY() + virtBounds.height() / 2
if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
virtBounds.centerY() - virtBounds.height() / 2
val width = pressedStateInnerBitmap.bounds.width() / 2
val height = pressedStateInnerBitmap.bounds.height() / 2
defaultStateInnerBitmap.setBounds(

View File

@@ -99,9 +99,7 @@ class GamesFragment : Fragment() {
}
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
if (shouldSwapData) {
(binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.games.value!!
)
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!)
gamesViewModel.setShouldSwapData(false)
}
}
@@ -130,9 +128,7 @@ class GamesFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)

View File

@@ -26,9 +26,6 @@ import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView
import java.io.File
import java.io.FilenameFilter
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -38,21 +35,19 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.*
import java.io.IOException
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
override var themeId: Int = 0
@@ -60,8 +55,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
settingsViewModel.settings.loadSettings()
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
@@ -86,9 +79,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
ThemeHelper.SYSTEM_BAR_ALPHA
)
)
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity(
MaterialColors.getColor(
@@ -174,9 +165,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding.navigationView.height.toFloat() * 2
translationY(0f)
} else {
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.navigationView.translationX =
binding.navigationView.width.toFloat() * -2
translationX(0f)
@@ -193,9 +182,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (smallLayout) {
translationY(binding.navigationView.height.toFloat() * 2)
} else {
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
translationX(binding.navigationView.width.toFloat() * -2)
} else {
translationX(binding.navigationView.width.toFloat() * 2)
@@ -240,9 +227,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
mlpStatusShade.height = insets.top
@@ -264,9 +249,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) {
if (result == null)
return@registerForActivityResult
}
contentResolver.takePersistableUriPermission(
result,
@@ -290,14 +274,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
if (result == null)
return@registerForActivityResult
}
if (!FileUtil.hasExtension(result, "keys")) {
if (!FileUtil.hasExtension(result.toString(), "keys")) {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_prod_keys_failure_extension_description
R.string.install_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
@@ -332,69 +315,15 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
val getFirmware =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val inputZip = contentResolver.openInputStream(result)
if (inputZip == null) {
Toast.makeText(
applicationContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
val task: () -> Any = {
var messageToShow: Any
try {
FileUtil.unzip(inputZip, cacheFirmwareDir)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
R.string.firmware_installed_failure,
R.string.firmware_installed_failure_description
)
} else {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true)
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
messageToShow = getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
val getAmiiboKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
if (result == null)
return@registerForActivityResult
}
if (!FileUtil.hasExtension(result, "bin")) {
if (!FileUtil.hasExtension(result.toString(), "bin")) {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_amiibo_keys_failure_extension_description
R.string.install_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
@@ -430,9 +359,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getDriver =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
if (result == null)
return@registerForActivityResult
}
val takeFlags =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -480,63 +408,4 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
}
val installGameUpdate =
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
this@MainActivity,
R.string.install_game_content
) {
val result = NativeLibrary.installFileToNand(it.toString())
lifecycleScope.launch {
withContext(Dispatchers.Main) {
when (result) {
NativeLibrary.InstallFileToNandResult.Success -> {
Toast.makeText(
applicationContext,
R.string.install_game_content_success,
Toast.LENGTH_SHORT
).show()
}
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
Toast.makeText(
applicationContext,
R.string.install_game_content_success_overwrite,
Toast.LENGTH_SHORT
).show()
}
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
MessageDialogFragment.newInstance(
R.string.install_game_content_failure,
R.string.install_game_content_failure_base
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
MessageDialogFragment.newInstance(
R.string.install_game_content_failure,
R.string.install_game_content_failure_file_extension,
R.string.install_game_content_help_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
else -> {
MessageDialogFragment.newInstance(
R.string.install_game_content_failure,
R.string.install_game_content_failure_description,
R.string.install_game_content_help_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
}
}
}
return@newInstance result
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}

View File

@@ -19,9 +19,7 @@ class ControllerMappingHelper {
// The two analog triggers generate analog motion events as well as a keycode.
// We always prefer to use the analog values, so throw away the button press
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
} else {
false
}
} else false
}
/**

View File

@@ -4,8 +4,8 @@
package org.yuzu.yuzu_emu.utils
import android.content.Context
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
object DirectoryInitialization {
private var userPath: String? = null

View File

@@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.util.*
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import java.io.File
import java.util.*
class DocumentsTree {
private var root: DocumentsNode? = null
@@ -29,20 +29,13 @@ class DocumentsTree {
val node = resolvePath(filepath)
return if (node == null || node.isDirectory) {
0
} else {
FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
}
} else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
}
fun exists(filepath: String): Boolean {
return resolvePath(filepath) != null
}
fun isDirectory(filepath: String): Boolean {
val node = resolvePath(filepath)
return node != null && node.isDirectory
}
private fun resolvePath(filepath: String): DocumentsNode? {
val tokens = StringTokenizer(filepath, File.separator, false)
var iterator = root
@@ -113,9 +106,7 @@ class DocumentsTree {
fun isNativePath(path: String): Boolean {
return if (path.isNotEmpty()) {
path[0] == '/'
} else {
false
}
} else false
}
}
}

View File

@@ -11,6 +11,14 @@ object EmulationMenuSettings {
private val preferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
// These must match what is defined in src/core/settings.h
const val LayoutOption_Default = 0
const val LayoutOption_SingleScreen = 1
const val LayoutOption_LargeScreen = 2
const val LayoutOption_SideScreen = 3
const val LayoutOption_MobilePortrait = 4
const val LayoutOption_MobileLandscape = 5
var joystickRelCenter: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
set(value) {
@@ -33,6 +41,16 @@ object EmulationMenuSettings {
.apply()
}
var screenLayout: Int
get() = preferences.getInt(
Settings.PREF_MENU_SETTINGS_SCREEN_LAYOUT,
LayoutOption_MobileLandscape
)
set(value) {
preferences.edit()
.putInt(Settings.PREF_MENU_SETTINGS_SCREEN_LAYOUT, value)
.apply()
}
var showFps: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
set(value) {

View File

@@ -7,18 +7,12 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import androidx.documentfile.provider.DocumentFile
import java.io.BufferedInputStream
import java.io.File
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.URLDecoder
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object FileUtil {
const val PATH_TREE = "tree"
@@ -282,34 +276,6 @@ object FileUtil {
return false
}
/**
* Extracts the given zip file into the given directory.
* @exception IOException if the file was being created outside of the target directory
*/
@Throws(SecurityException::class)
fun unzip(zipStream: InputStream, destDir: File): Boolean {
ZipInputStream(BufferedInputStream(zipStream)).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
val entryName = entry.name
val entryFile = File(destDir, entryName)
if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
throw SecurityException("Entry is outside of the target dir: " + entryFile.name)
}
if (entry.isDirectory) {
entryFile.mkdirs()
} else {
entryFile.parentFile?.mkdirs()
entryFile.createNewFile()
entryFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
}
}
return true
}
fun isRootTreeUri(uri: Uri): Boolean {
val paths = uri.pathSegments
return paths.size == 2 && PATH_TREE == paths[0]
@@ -326,25 +292,7 @@ object FileUtil {
}
}
fun hasExtension(path: String, extension: String): Boolean =
path.substring(path.lastIndexOf(".") + 1).contains(extension)
fun hasExtension(uri: Uri, extension: String): Boolean {
val fileName: String?
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor?.moveToFirst()
if (nameIndex == null) {
return false
}
fileName = cursor.getString(nameIndex)
cursor.close()
if (fileName == null) {
return false
}
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
fun hasExtension(path: String, extension: String): Boolean {
return path.substring(path.lastIndexOf(".") + 1).contains(extension)
}
}

View File

@@ -54,7 +54,7 @@ class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) {
return START_NOT_STICKY
return START_NOT_STICKY;
}
if (intent.action == ACTION_STOP) {
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)

View File

@@ -6,12 +6,13 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
import java.util.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
import java.util.*
object GameHelper {
const val KEY_GAME_PATH = "game_path"
@@ -82,8 +83,7 @@ object GameHelper {
NativeLibrary.getRegions(filePath),
filePath,
gameId,
NativeLibrary.getCompany(filePath),
NativeLibrary.isHomebrew(filePath)
NativeLibrary.getCompany(filePath)
)
val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)

View File

@@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.net.Uri
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json"
@@ -113,8 +113,6 @@ object GpuDriverHelper {
initializeDriverParameters(context)
}
external fun supportsCustomDriverLoading(): Boolean
// Parse the custom driver metadata to retrieve the name.
val customDriverName: String?
get() {

View File

@@ -3,12 +3,12 @@
package org.yuzu.yuzu_emu.utils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import org.json.JSONException
import org.json.JSONObject
class GpuDriverMetadata(metadataFilePath: String) {
var name: String? = null

View File

@@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils
import android.view.KeyEvent
import android.view.MotionEvent
import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary
import kotlin.math.sqrt
class InputHandler {
fun initialize() {
@@ -68,11 +68,7 @@ class InputHandler {
6 -> NativeLibrary.Player6Device
7 -> NativeLibrary.Player7Device
8 -> NativeLibrary.Player8Device
else -> if (NativeLibrary.isHandheldOnly()) {
NativeLibrary.ConsoleDevice
} else {
NativeLibrary.Player1Device
}
else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
}
}
@@ -111,11 +107,7 @@ class InputHandler {
}
private fun getAxisToButton(axis: Float): Int {
return if (axis > 0.5f) {
NativeLibrary.ButtonState.PRESSED
} else {
NativeLibrary.ButtonState.RELEASED
}
return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
}
private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
@@ -295,6 +287,7 @@ class InputHandler {
}
}
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
// Joycon support is half dead. Right joystick doesn't work
val playerNumber = getPlayerNumber(event.device.controllerNumber)
@@ -362,4 +355,6 @@ class InputHandler {
)
}
}
}
}

View File

@@ -4,7 +4,9 @@
package org.yuzu.yuzu_emu.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.Rect
object InsetsHelper {
const val THREE_BUTTON_NAVIGATION = 0
@@ -18,8 +20,12 @@ object InsetsHelper {
resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
return if (resourceId != 0) {
resources.getInteger(resourceId)
} else {
0
}
} else 0
}
fun getBottomPaddingRequired(activity: Activity): Int {
val visibleFrame = Rect()
activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
}
}

View File

@@ -13,8 +13,8 @@ import android.nfc.tech.NfcA
import android.os.Build
import android.os.Handler
import android.os.Looper
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
class NfcReader(private val activity: Activity) {
private var nfcAdapter: NfcAdapter? = null
@@ -25,13 +25,10 @@ class NfcReader(private val activity: Activity) {
pendingIntent = PendingIntent.getActivity(
activity,
0,
Intent(activity, activity.javaClass),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
0, Intent(activity, activity.javaClass),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
else PendingIntent.FLAG_UPDATE_CURRENT
)
val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
@@ -48,9 +45,9 @@ class NfcReader(private val activity: Activity) {
fun onNewIntent(intent: Intent) {
val action = intent.action
if (NfcAdapter.ACTION_TAG_DISCOVERED != action &&
NfcAdapter.ACTION_TECH_DISCOVERED != action &&
NfcAdapter.ACTION_NDEF_DISCOVERED != action
if (NfcAdapter.ACTION_TAG_DISCOVERED != action
&& NfcAdapter.ACTION_TECH_DISCOVERED != action
&& NfcAdapter.ACTION_NDEF_DISCOVERED != action
) {
return
}
@@ -87,7 +84,7 @@ class NfcReader(private val activity: Activity) {
}
private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
val bufferSize = amiibo.maxTransceiveLength
val bufferSize = amiibo.maxTransceiveLength;
val tagSize = 0x21C
val pageSize = 4
val lastPage = tagSize / pageSize - 1
@@ -106,7 +103,7 @@ class NfcReader(private val activity: Activity) {
val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
} catch (e: IOException) {
return null
return null;
}
}
return tagData

View File

@@ -11,34 +11,30 @@ import java.io.Serializable
object SerializableHelper {
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getSerializable(key, T::class.java)
} else {
else
getSerializable(key) as? T
}
}
inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getSerializableExtra(key, T::class.java)
} else {
else
getSerializableExtra(key) as? T
}
}
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getParcelable(key, T::class.java)
} else {
else
getParcelable(key) as? T
}
}
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getParcelableExtra(key, T::class.java)
} else {
else
getParcelableExtra(key) as? T
}
}
}

View File

@@ -3,19 +3,21 @@
package org.yuzu.yuzu_emu.utils
import android.app.Activity
import android.content.res.Configuration
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.preference.PreferenceManager
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.ui.main.ThemeProvider
import kotlin.math.roundToInt
object ThemeHelper {
const val SYSTEM_BAR_ALPHA = 0.9f
@@ -34,8 +36,8 @@ object ThemeHelper {
// Using a specific night mode check because this could apply incorrectly when using the
// light app mode, dark system mode, and black backgrounds. Launching the settings activity
// will then show light mode colors/navigation bars but with black backgrounds.
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
isNightMode(activity)
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
&& isNightMode(activity)
) {
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
}
@@ -44,10 +46,8 @@ object ThemeHelper {
@ColorInt
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
return Color.argb(
(alphaFactor * Color.alpha(color)).roundToInt(),
Color.red(color),
Color.green(color),
Color.blue(color)
(alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color),
Color.green(color), Color.blue(color)
)
}

View File

@@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.views
import android.content.Context
import android.util.AttributeSet
import android.util.Rational
import android.view.SurfaceView
import kotlin.math.roundToInt
class FixedRatioSurfaceView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : SurfaceView(context, attrs, defStyleAttr) {
private var aspectRatio: Float = 0f // (width / height), 0f is a special value for stretch
/**
* Sets the desired aspect ratio for this view
* @param ratio the ratio to force the view to, or null to stretch to fit
*/
fun setAspectRatio(ratio: Rational?) {
aspectRatio = ratio?.toFloat() ?: 0f
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
if (aspectRatio != 0f) {
val newWidth: Int
val newHeight: Int
if (height * aspectRatio < width) {
newWidth = (height * aspectRatio).roundToInt()
newHeight = height
} else {
newWidth = width
newHeight = (width / aspectRatio).roundToInt()
}
val left = (width - newWidth) / 2
val top = (height - newHeight) / 2
setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
} else {
setLeftTopRightBottom(0, 0, width, height)
}
}
}

View File

@@ -235,13 +235,9 @@ void Config::ReadValues() {
Settings::values.async_presentation =
config->GetBoolean("Renderer", "async_presentation", true);
// Disable force_max_clock by default on Android
// Enable force_max_clock by default on Android
Settings::values.renderer_force_max_clock =
config->GetBoolean("Renderer", "force_max_clock", false);
// Disable use_reactive_flushing by default on Android
Settings::values.use_reactive_flushing =
config->GetBoolean("Renderer", "use_reactive_flushing", false);
config->GetBoolean("Renderer", "force_max_clock", true);
// Audio
ReadSetting("Audio", Settings::values.sink_id);

View File

@@ -251,7 +251,7 @@ backend =
# 0: Off, 1 (default): On
async_presentation =
# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
# Enable graphics API debugging mode.
# 0 (default): Disabled, 1: Enabled
force_max_clock =
@@ -328,10 +328,6 @@ shader_backend =
# 0 (default): Off, 1: On
use_asynchronous_shaders =
# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
# 0 (default): Off, 1: On
use_reactive_flushing =
# NVDEC emulation.
# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
nvdec_emulation =

View File

@@ -13,7 +13,6 @@
#include <android/api-level.h>
#include <android/native_window_jni.h>
#include <core/loader/nro.h>
#include "common/detached_tasks.h"
#include "common/dynamic_library.h"
@@ -28,10 +27,7 @@
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
@@ -97,72 +93,12 @@ public:
m_native_window = native_window;
}
int InstallFileToNand(std::string filename) {
const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
std::size_t block_size) {
if (src == nullptr || dest == nullptr) {
return false;
}
if (!dest->Resize(src->GetSize())) {
return false;
}
u32 ScreenRotation() const {
return m_screen_rotation;
}
using namespace Common::Literals;
std::vector<u8> buffer(1_MiB);
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
const auto read = src->Read(buffer.data(), buffer.size(), i);
dest->Write(buffer.data(), read, i);
}
return true;
};
enum InstallResult {
Success = 0,
SuccessFileOverwritten = 1,
InstallError = 2,
ErrorBaseGame = 3,
ErrorFilenameExtension = 4,
};
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
std::shared_ptr<FileSys::NSP> nsp;
if (filename.ends_with("nsp")) {
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
if (nsp->IsExtractedType()) {
return InstallError;
}
} else if (filename.ends_with("xci")) {
const auto xci =
std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
nsp = xci->GetSecurePartitionNSP();
} else {
return ErrorFilenameExtension;
}
if (!nsp) {
return InstallError;
}
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
return InstallError;
}
const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
*nsp, true, copy_func);
switch (res) {
case FileSys::InstallResult::Success:
return Success;
case FileSys::InstallResult::OverwriteExisting:
return SuccessFileOverwritten;
case FileSys::InstallResult::ErrorBaseInstall:
return ErrorBaseGame;
default:
return InstallError;
}
void SetScreenRotation(u32 screen_rotation) {
m_screen_rotation = screen_rotation;
}
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
@@ -202,11 +138,6 @@ public:
return m_is_running;
}
bool IsPaused() const {
std::scoped_lock lock(m_mutex);
return m_is_running && m_is_paused;
}
const Core::PerfStatsResults& PerfStats() const {
std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
return m_perf_stats;
@@ -230,14 +161,14 @@ public:
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
m_vulkan_library);
m_system.SetFilesystem(m_vfs);
// Initialize system.
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
m_software_keyboard = android_keyboard.get();
m_system.SetShuttingDown(false);
m_system.ApplySettings();
m_system.HIDCore().ReloadInputDevices();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
m_system.SetAppletFrontendSet({
nullptr, // Amiibo Settings
nullptr, // Controller Selector
@@ -249,8 +180,7 @@ public:
std::move(android_keyboard), // Software Keyboard
nullptr, // Web Browser
});
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem());
// Initialize account manager
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@@ -292,13 +222,11 @@ public:
void PauseEmulation() {
std::scoped_lock lock(m_mutex);
m_system.Pause();
m_is_paused = true;
}
void UnPauseEmulation() {
std::scoped_lock lock(m_mutex);
m_system.Run();
m_is_paused = false;
}
void HaltEmulation() {
@@ -353,10 +281,6 @@ public:
return GetRomMetadata(path).icon;
}
bool GetIsHomebrew(const std::string& path) {
return GetRomMetadata(path).isHomebrew;
}
void ResetRomMetadata() {
m_rom_metadata_cache.clear();
}
@@ -424,7 +348,6 @@ private:
struct RomMetadata {
std::string title;
std::vector<u8> icon;
bool isHomebrew;
};
RomMetadata GetRomMetadata(const std::string& path) {
@@ -437,17 +360,11 @@ private:
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file = Core::GetGameFileFromPath(m_vfs, path);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon);
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
@@ -471,16 +388,16 @@ private:
// Window management
std::unique_ptr<EmuWindow_Android> m_window;
ANativeWindow* m_native_window{};
u32 m_screen_rotation{};
// Core emulation
Core::System m_system;
InputCommon::InputSubsystem m_input_subsystem;
Common::DetachedTasks m_detached_tasks;
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
bool m_is_running{};
bool m_is_paused{};
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
@@ -497,6 +414,10 @@ private:
} // Anonymous namespace
u32 GetAndroidScreenRotation() {
return EmulationSession::GetInstance().ScreenRotation();
}
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
@@ -540,18 +461,19 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
EmulationSession::GetInstance().SurfaceChanged();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jint layout_option,
jint rotation) {
return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring j_directory) {
Common::FS::SetAppDirectory(GetJString(env, j_directory));
}
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring j_file) {
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
}
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
jstring custom_driver_name, jstring file_redirect_dir) {
@@ -560,26 +482,6 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
}
[[maybe_unused]] static bool CheckKgslPresent() {
constexpr auto KgslPath{"/dev/kgsl-3d0"};
return access(KgslPath, F_OK) == 0;
}
[[maybe_unused]] bool SupportsCustomDriver() {
return android_get_device_api_level() >= 28 && CheckKgslPresent();
}
jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject instance) {
#ifdef ARCHITECTURE_arm64
// If the KGSL device exists custom drivers can be loaded using adrenotools
return SupportsCustomDriver();
#else
return false;
#endif
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
[[maybe_unused]] jclass clazz) {
Core::Crypto::KeyManager::Instance().ReloadKeys();
@@ -611,11 +513,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz) {
return EmulationSession::GetInstance().IsHandheldOnly();
@@ -765,12 +662,6 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
return env->NewStringUTF("");
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz,
[[maybe_unused]] jstring j_filename) {
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
// Create the default config.ini.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L840,280Q857,280 868.5,291.5Q880,303 880,320Q880,337 868.5,348.5Q857,360 840,360L800,360L800,440L840,440Q857,440 868.5,451.5Q880,463 880,480Q880,497 868.5,508.5Q857,520 840,520L800,520L800,600L840,600Q857,600 868.5,611.5Q880,623 880,640Q880,657 868.5,668.5Q857,680 840,680L800,680L800,760Q800,793 776.5,816.5Q753,840 720,840L160,840ZM160,760L720,760Q720,760 720,760Q720,760 720,760L720,200Q720,200 720,200Q720,200 720,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM280,680L400,680Q417,680 428.5,668.5Q440,657 440,640L440,560Q440,543 428.5,531.5Q417,520 400,520L280,520Q263,520 251.5,531.5Q240,543 240,560L240,640Q240,657 251.5,668.5Q263,680 280,680ZM520,400L600,400Q617,400 628.5,388.5Q640,377 640,360L640,320Q640,303 628.5,291.5Q617,280 600,280L520,280Q503,280 491.5,291.5Q480,303 480,320L480,360Q480,377 491.5,388.5Q503,400 520,400ZM280,480L400,480Q417,480 428.5,468.5Q440,457 440,440L440,320Q440,303 428.5,291.5Q417,280 400,280L280,280Q263,280 251.5,291.5Q240,303 240,320L240,440Q240,457 251.5,468.5Q263,480 280,480ZM520,680L600,680Q617,680 628.5,668.5Q640,657 640,640L640,480Q640,463 628.5,451.5Q617,440 600,440L520,440Q503,440 491.5,451.5Q480,463 480,480L480,640Q480,657 491.5,668.5Q503,680 520,680ZM160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200Z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M360,720L600,720Q617,720 628.5,708.5Q640,697 640,680Q640,663 628.5,651.5Q617,640 600,640L360,640Q343,640 331.5,651.5Q320,663 320,680Q320,697 331.5,708.5Q343,720 360,720ZM360,560L600,560Q617,560 628.5,548.5Q640,537 640,520Q640,503 628.5,491.5Q617,480 600,480L360,480Q343,480 331.5,491.5Q320,503 320,520Q320,537 331.5,548.5Q343,560 360,560ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L527,80Q543,80 557.5,86Q572,92 583,103L777,297Q788,308 794,322.5Q800,337 800,353L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,320L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L560,360Q543,360 531.5,348.5Q520,337 520,320ZM240,160L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,160L240,320Q240,337 240,348.5Q240,360 240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:pathData="M8,5v14l11,-7z" />
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h250v60L140,220v520h680v-520L570,220v-60h250q24,0 42,18t18,42v520q0,24 -18,42t-42,18L140,800ZM480,615L280,415l43,-43 127,127v-339h60v339l127,-127 43,43 -200,200Z"/>
</vector>

View File

@@ -1,9 +1,13 @@
<androidx.fragment.app.FragmentContainerView
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="@+id/frame_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
app:defaultNavHost="true" />
android:keepScreenOn="true">
<FrameLayout
android:id="@+id/frame_emulation_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginHorizontal="16dp">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.HeadlineLarge"
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
tools:text="@string/license_adreno_tools" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyLarge"
android:id="@+id/text_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="16dp"
android:autoLink="all"
tools:text="@string/license_adreno_tools_link" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyLarge"
android:id="@+id/text_copyright"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="16dp"
android:textStyle="bold"
tools:text="@string/license_adreno_tools_copyright" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:id="@+id/text_license"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginVertical="16dp"
android:autoLink="all"
tools:text="@string/license_adreno_tools_text" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -109,39 +109,6 @@
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_licenses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp"
android:paddingHorizontal="16dp"
android:background="?attr/selectableItemBackground"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart"
android:text="@string/licenses" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
android:text="@string/licenses_description" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -12,65 +12,48 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/emulation_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is what everything is rendered to during emulation -->
<org.yuzu.yuzu_emu.views.FixedRatioSurfaceView
android:id="@+id/surface_emulation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="false"
android:focusableInTouchMode="false" />
</FrameLayout>
<FrameLayout
android:id="@+id/input_container"
<!-- This is what everything is rendered to during emulation -->
<SurfaceView
android:id="@+id/surface_emulation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom">
<!-- This is the onscreen input overlay -->
<org.yuzu.yuzu_emu.overlay.InputOverlay
android:id="@+id/surface_input_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
android:focusableInTouchMode="true" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/emulation_done"
android:visibility="gone" />
</FrameLayout>
android:focusable="false"
android:focusableInTouchMode="false" />
<FrameLayout
android:id="@+id/overlay_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_gravity="bottom">
<TextView
android:id="@+id/show_fps_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:clickable="false"
android:focusable="false"
android:shadowColor="@android:color/black"
android:textColor="@android:color/white"
android:textSize="12sp"
tools:ignore="RtlHardcoded" />
<!-- This is the onscreen input overlay -->
<org.yuzu.yuzu_emu.overlay.InputOverlay
android:id="@+id/surface_input_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true" />
<TextView
android:id="@+id/show_fps_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:clickable="false"
android:focusable="false"
android:shadowColor="@android:color/black"
android:textColor="@android:color/white"
android:textSize="12sp"
tools:ignore="RtlHardcoded" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/emulation_done"
android:visibility="gone" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_licenses"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_licenses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_licenses"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/licenses"
app:navigationIcon="@drawable/ic_back" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_licenses"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:minHeight="72dp"
android:paddingVertical="@dimen/spacing_large"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="24dp">
android:paddingEnd="24dp"
android:paddingVertical="@dimen/spacing_large">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_widget"
@@ -19,35 +19,32 @@
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodySmall"
android:id="@+id/text_setting_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignStart="@+id/text_setting_name"
android:layout_below="@+id/text_setting_name"
android:layout_marginEnd="@dimen/spacing_large"
android:layout_marginTop="@dimen/spacing_small"
android:layout_toStartOf="@+id/switch_widget"
android:textAlignment="viewStart"
tools:text="@string/frame_limit_enable_description" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.HeadlineMedium"
android:id="@+id/text_setting_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/spacing_large"
android:layout_toStartOf="@+id/switch_widget"
android:gravity="center_vertical"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_setting_name"
style="@style/TextAppearance.Material3.HeadlineMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="16sp"
app:lineHeight="28dp"
tools:text="@string/frame_limit_enable" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_setting_description"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:textAlignment="viewStart"
tools:text="@string/frame_limit_enable_description" />
</LinearLayout>
android:textSize="16sp"
android:textAlignment="viewStart"
app:lineHeight="28dp"
tools:text="@string/frame_limit_enable" />
</RelativeLayout>

View File

@@ -1,14 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text_header_name"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingHorizontal="@dimen/spacing_large"
android:paddingVertical="16dp"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textStyle="bold"
tools:text="CPU Settings" />
android:layout_height="48dp"
android:paddingVertical="4dp"
android:paddingHorizontal="@dimen/spacing_large">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleSmall"
android:id="@+id/text_header_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:textColor="?attr/colorPrimary"
android:textAlignment="viewStart"
android:textStyle="bold"
tools:text="CPU Settings" />
</FrameLayout>

View File

@@ -6,6 +6,26 @@
android:title="@string/emulation_fps_counter"
android:checkable="true" />
<item
android:id="@+id/menu_screen_layout"
android:title="@string/emulation_screen_rotation">
<menu>
<group
android:id="@+id/menu_screen_layout_group"
android:checkableBehavior="single">
<item
android:id="@+id/menu_screen_layout_landscape"
android:title="@string/emulation_screen_rotation_landscape"/>
<item
android:id="@+id/menu_screen_layout_portrait"
android:title="@string/emulation_screen_rotation_portrait"/>
<item
android:id="@+id/menu_screen_layout_auto"
android:title="@string/emulation_screen_rotation_auto"/>
</group>
</menu>
</item>
<item
android:id="@+id/menu_edit_overlay"
android:title="@string/emulation_touch_overlay_edit" />

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emulation_navigation"
app:startDestination="@id/emulationFragment">
<fragment
android:id="@+id/emulationFragment"
android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment"
android:label="fragment_emulation"
tools:layout="@layout/fragment_emulation" >
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game" />
</fragment>
</navigation>

Some files were not shown because too many files have changed in this diff Show More