diff --git a/.ci/windows/install-msvc.ps1 b/.ci/windows/install-msvc.ps1
index b88f727ed8..788b2848ad 100755
--- a/.ci/windows/install-msvc.ps1
+++ b/.ci/windows/install-msvc.ps1
@@ -10,7 +10,7 @@ if (-not ([bool](net session 2>$null))) {
}
$VSVer = "17"
-$ExeFile = "vs_BuildTools.exe"
+$ExeFile = "vs_community.exe"
$Uri = "https://aka.ms/vs/$VSVer/release/$ExeFile"
$Destination = "./$ExeFile"
@@ -19,21 +19,39 @@ $WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile($Uri, $Destination)
Write-Host "Finished downloading $ExeFile"
-$VSROOT = "C:/VSBuildTools/$VSVer"
$Arguments = @(
- "--installPath `"$VSROOT`"", # set custom installation path
- "--quiet", # suppress UI
- "--wait", # wait for installation to complete
- "--norestart", # prevent automatic restart
- "--add Microsoft.VisualStudio.Workload.VCTools", # add C++ build tools workload
- "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64", # add core x86/x64 C++ tools
- "--add Microsoft.VisualStudio.Component.Windows10SDK.19041" # add specific Windows SDK
+ "--quiet", # Suppress installer UI
+ "--wait", # Wait for installation to complete
+ "--norestart", # Prevent automatic restart
+ "--force", # Force installation even if components are already installed
+ "--add Microsoft.VisualStudio.Workload.NativeDesktop", # Desktop development with C++
+ "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64", # Core C++ compiler/tools for x86/x64
+ "--add Microsoft.VisualStudio.Component.Windows11SDK.26100",# Windows 11 SDK (26100)
+ "--add Microsoft.VisualStudio.Component.Windows10SDK.19041",# Windows 10 SDK (19041)
+ "--add Microsoft.VisualStudio.Component.VC.Llvm.Clang", # LLVM Clang compiler
+ "--add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", # LLVM Clang integration toolset
+ "--add Microsoft.VisualStudio.Component.Windows11SDK.22621",# Windows 11 SDK (22621)
+ "--add Microsoft.VisualStudio.Component.VC.CMake.Project", # CMake project support
+ "--add Microsoft.VisualStudio.ComponentGroup.VC.Tools.142.x86.x64", # VC++ 14.2 toolset
+ "--add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang" # LLVM Clang for native desktop
)
Write-Host "Installing Visual Studio Build Tools"
-$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments
-$ExitCode = $InstallProcess.ExitCode
+$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -ArgumentList $Arguments
+# Spinner while installing
+$Spinner = "|/-\"
+$i = 0
+while (-not $InstallProcess.HasExited) {
+ Write-Host -NoNewline ("`rInstalling... " + $Spinner[$i % $Spinner.Length])
+ Start-Sleep -Milliseconds 250
+ $i++
+}
+
+# Clear spinner line
+Write-Host "`rSetup completed! "
+
+$ExitCode = $InstallProcess.ExitCode
if ($ExitCode -ne 0) {
Write-Host "Error installing Visual Studio Build Tools (Error: $ExitCode)"
Exit $ExitCode
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ef3c0bef6e..3599105020 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -139,65 +139,50 @@ endif()
# Set bundled sdl2/qt as dependent options.
# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
-CMAKE_DEPENDENT_OPTION(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF)
-
-set(EXT_DEFAULT OFF)
-
-if (MSVC OR ANDROID)
- set(EXT_DEFAULT ON)
-endif()
+cmake_dependent_option(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF)
if (ENABLE_SDL2)
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
- CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF)
+ cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF)
option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}")
endif()
+option(ENABLE_QT "Enable the Qt frontend" ON)
+option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
+option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF)
+cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
+option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
+option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
+set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
+
+option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
+
+set(EXT_DEFAULT OFF)
+if (MSVC OR ANDROID)
+ set(EXT_DEFAULT ON)
+endif()
+option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
+
+option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
+cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from source" OFF "NOT WIN32 AND NOT ANDROID" OFF)
+
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
-option(ENABLE_QT "Enable the Qt frontend" ON)
-option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
-option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF)
-
-CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
-
-option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
-
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_WIFI_SCAN "Enable WiFi scanning" OFF)
-option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
-cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from source" OFF "NOT WIN32 AND NOT ANDROID" OFF)
-
-option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
-
-option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
-
-set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
-
-option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
-
-CMAKE_DEPENDENT_OPTION(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF "ENABLE_QT" OFF)
+cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF "ENABLE_QT" OFF)
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
-option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ${EXT_DEFAULT})
-
-# TODO(crueter): CI this?
-option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
-
-
-CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
-
-CMAKE_DEPENDENT_OPTION(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
-
-CMAKE_DEPENDENT_OPTION(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF)
-
-CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
-
+option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF)
+if (YUZU_USE_PRECOMPILED_HEADERS)
+ message(STATUS "Using Precompiled Headers.")
+ set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
+endif()
option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
if(YUZU_ENABLE_LTO)
include(CheckIPOSupported)
@@ -205,17 +190,42 @@ if(YUZU_ENABLE_LTO)
if(NOT COMPILER_SUPPORTS_LTO)
message(FATAL_ERROR "Your compiler does not support interprocedural optimization (IPO). Re-run CMake with -DYUZU_ENABLE_LTO=OFF.")
endif()
+ set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO})
endif()
+option(USE_CCACHE "Use ccache for compilation" OFF)
+set(CCACHE_PATH "ccache" CACHE STRING "Path to ccache binary")
+if(USE_CCACHE)
+ find_program(CCACHE_BINARY ${CCACHE_PATH})
+ if(CCACHE_BINARY)
+ message(STATUS "Found ccache at: ${CCACHE_BINARY}")
+ set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_BINARY})
+ set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_BINARY})
+ if (YUZU_USE_PRECOMPILED_HEADERS)
+ message(FATAL_ERROR "Precompiled headers are incompatible with ccache. Re-run CMake with -DYUZU_USE_PRECOMPILED_HEADERS=OFF.")
+ endif()
+ else()
+ message(WARNING "USE_CCACHE enabled, but no executable found at: ${CCACHE_PATH}")
+ endif()
+endif()
+
+# TODO(crueter): CI this?
+option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
+
+cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
+cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
+
+cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF)
+
+cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON)
-
-CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
-
-CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
-
set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database")
+cmake_dependent_option(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "LINUX" OFF)
+
+cmake_dependent_option(YUZU_APPLE_USE_BUNDLED_MONTENVK "Download bundled MoltenVK lib" ON "APPLE" OFF)
+
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
set(DEFAULT_ENABLE_OPENSSL ON)
@@ -228,15 +238,12 @@ if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN)
# your own copy of it.
set(DEFAULT_ENABLE_OPENSSL OFF)
endif()
-
if (ENABLE_WEB_SERVICE)
set(DEFAULT_ENABLE_OPENSSL ON)
endif()
-
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
-
if (ENABLE_OPENSSL)
- CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" "${MSVC}" "NOT ANDROID" ON)
+ cmake_dependent_option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" "${MSVC}" "NOT ANDROID" ON)
endif()
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
@@ -263,21 +270,6 @@ if (ANDROID)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # Workaround for Oboe
endif()
-if (YUZU_USE_PRECOMPILED_HEADERS)
- if (MSVC AND CCACHE)
- # buildcache does not properly cache PCH files, leading to compilation errors.
- # See https://github.com/mbitsnbites/buildcache/discussions/230
- message(WARNING "buildcache does not properly support Precompiled Headers. Disabling PCH")
- set(DYNARMIC_USE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE)
- set(YUZU_USE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE)
- endif()
-endif()
-
-if (YUZU_USE_PRECOMPILED_HEADERS)
- message(STATUS "Using Precompiled Headers.")
- set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
-endif()
-
# Default to a Release build
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
@@ -894,25 +886,47 @@ if (MSVC AND CXX_CLANG)
link_libraries(llvm-mingw-runtime)
endif()
+#[[
+ search order:
+ - gold (GCC only) - the best, generally, but unfortunately not packaged anymore
+ - mold (GCC only) - generally does well on GCC
+ - ldd - preferred on clang
+ - bfd - the final fallback
+ - If none are found (macOS uses ld.prime, etc) just use the default linker
+]]
if (YUZU_USE_FASTER_LD)
- # clang should always use lld
- find_program(LLD lld)
+ find_program(LINKER_BFD bfd)
+ if (LINKER_BFD)
+ set(LINKER bfd)
+ endif()
- if (LLD)
+ find_program(LINKER_LLD lld)
+ if (LINKER_LLD)
set(LINKER lld)
endif()
- # GNU appears to work better with mold
- # TODO: mold has been slow lately, see if better options exist (search for gold?)
if (CXX_GCC)
- find_program(MOLD mold)
- if (MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
+ find_program(LINKER_MOLD mold)
+ if (LINKER_MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
set(LINKER mold)
endif()
+
+ find_program(LINKER_GOLD gold)
+ if (LINKER_GOLD)
+ set(LINKER gold)
+ endif()
endif()
- message(NOTICE "Selecting ${LINKER} as linker")
- add_link_options("-fuse-ld=${LINKER}")
+ if (LINKER)
+ message(NOTICE "Selecting ${LINKER} as linker")
+ add_link_options("-fuse-ld=${LINKER}")
+ else()
+ message(WARNING "No faster linker found--using default")
+ endif()
+
+ if (LINKER STREQUAL "lld" AND CXX_GCC)
+ message(WARNING "Using lld on GCC may cause issues with certain LTO settings. If the program fails to compile, disable YUZU_USE_FASTER_LD, or install mold or GNU gold.")
+ endif()
endif()
# Set runtime library to MD/MDd for all configurations
diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake
index 6c4afc03be..f6e3aaa4ad 100644
--- a/CMakeModules/DownloadExternals.cmake
+++ b/CMakeModules/DownloadExternals.cmake
@@ -10,6 +10,7 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi
set(package_base_url "https://github.com/eden-emulator/")
set(package_repo "no_platform")
set(package_extension "no_platform")
+ set(CACHE_KEY "")
# TODO(crueter): Need to convert ffmpeg to a CI.
if (WIN32 OR FORCE_WIN_ARCHIVES)
@@ -33,8 +34,9 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi
else()
message(FATAL_ERROR "No package available for this platform")
endif()
- set(package_url "${package_base_url}${package_repo}")
- set(full_url ${package_url}${remote_path}${lib_name}${package_extension})
+ string(CONCAT package_url "${package_base_url}" "${package_repo}")
+ string(CONCAT full_url "${package_url}" "${remote_path}" "${lib_name}" "${package_extension}")
+ message(STATUS "Resolved bundled URL: ${full_url}")
# TODO(crueter): DELETE THIS ENTIRELY, GLORY BE TO THE CI!
AddPackage(
@@ -47,26 +49,12 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi
# TODO(crueter): hash
)
- set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE)
- message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}")
-endfunction()
-
-function(download_moltenvk_external platform version)
- set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
- set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
- if (NOT EXISTS ${MOLTENVK_DIR})
- if (NOT EXISTS ${MOLTENVK_TAR})
- file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar
- ${MOLTENVK_TAR} SHOW_PROGRESS)
- endif()
-
- execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
- WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
+ if (DEFINED ${cpm_key}_SOURCE_DIR)
+ set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE)
+ message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}")
+ else()
+ message(FATAL_ERROR "AddPackage did not set ${cpm_key}_SOURCE_DIR")
endif()
-
- # Add the MoltenVK library path to the prefix so find_library can locate it.
- list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}")
- set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()
# Determine installation parameters for OS, architecture, and compiler
@@ -108,7 +96,7 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
set(host "linux")
set(type "desktop")
set(arch "linux_gcc_64")
- set(arch_path "linux")
+ set(arch_path "gcc_64")
endif()
set(${host_out} "${host}" PARENT_SCOPE)
@@ -143,56 +131,79 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
if (tool)
set(prefix "${base_path}/Tools")
- set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
+ list(APPEND install_args install-tool --outputdir "${base_path}" "${host}" desktop "${target}")
else()
set(prefix "${base_path}/${target}/${arch_path}")
- set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} -m qt_base)
+ list(APPEND install_args install-qt --outputdir "${base_path}" "${host}" "${type}" "${target}" "${arch}" -m qt_base)
if (YUZU_USE_QT_MULTIMEDIA)
- set(install_args ${install_args} qtmultimedia)
+ list(APPEND install_args qtmultimedia)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
- set(install_args ${install_args} qtpositioning qtwebchannel qtwebengine)
+ list(APPEND install_args qtpositioning qtwebchannel qtwebengine)
endif()
- if (NOT ${YUZU_QT_MIRROR} STREQUAL "")
+ if (NOT "${YUZU_QT_MIRROR}" STREQUAL "")
message(STATUS "Using Qt mirror ${YUZU_QT_MIRROR}")
- set(install_args ${install_args} -b ${YUZU_QT_MIRROR})
+ list(APPEND install_args -b "${YUZU_QT_MIRROR}")
endif()
endif()
- message(STATUS "Install Args ${install_args}")
+ message(STATUS "Install Args: ${install_args}")
+
if (NOT EXISTS "${prefix}")
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0")
if (WIN32)
set(aqt_path "${base_path}/aqt.exe")
if (NOT EXISTS "${aqt_path}")
- file(DOWNLOAD
- ${AQT_PREBUILD_BASE_URL}/aqt.exe
- ${aqt_path} SHOW_PROGRESS)
+ file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt.exe" "${aqt_path}" SHOW_PROGRESS)
+ endif()
+ execute_process(COMMAND "${aqt_path}" ${install_args}
+ WORKING_DIRECTORY "${base_path}"
+ RESULT_VARIABLE aqt_res
+ OUTPUT_VARIABLE aqt_out
+ ERROR_VARIABLE aqt_err)
+ if (NOT aqt_res EQUAL 0)
+ message(FATAL_ERROR "aqt.exe failed: ${aqt_err}")
endif()
- execute_process(COMMAND ${aqt_path} ${install_args}
- WORKING_DIRECTORY ${base_path})
elseif (APPLE)
set(aqt_path "${base_path}/aqt-macos")
if (NOT EXISTS "${aqt_path}")
- file(DOWNLOAD
- ${AQT_PREBUILD_BASE_URL}/aqt-macos
- ${aqt_path} SHOW_PROGRESS)
+ file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt-macos" "${aqt_path}" SHOW_PROGRESS)
+ endif()
+ execute_process(COMMAND chmod +x "${aqt_path}")
+ execute_process(COMMAND "${aqt_path}" ${install_args}
+ WORKING_DIRECTORY "${base_path}"
+ RESULT_VARIABLE aqt_res
+ ERROR_VARIABLE aqt_err)
+ if (NOT aqt_res EQUAL 0)
+ message(FATAL_ERROR "aqt-macos failed: ${aqt_err}")
endif()
- execute_process(COMMAND chmod +x ${aqt_path})
- execute_process(COMMAND ${aqt_path} ${install_args}
- WORKING_DIRECTORY ${base_path})
else()
+ find_program(PYTHON3_EXECUTABLE python3)
+ if (NOT PYTHON3_EXECUTABLE)
+ message(FATAL_ERROR "python3 is required to install Qt using aqt (pip mode).")
+ endif()
set(aqt_install_path "${base_path}/aqt")
file(MAKE_DIRECTORY "${aqt_install_path}")
- execute_process(COMMAND python3 -m pip install --target=${aqt_install_path} aqtinstall
- WORKING_DIRECTORY ${base_path})
- execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
- WORKING_DIRECTORY ${base_path})
+ execute_process(COMMAND "${PYTHON3_EXECUTABLE}" -m pip install --target="${aqt_install_path}" aqtinstall
+ WORKING_DIRECTORY "${base_path}"
+ RESULT_VARIABLE pip_res
+ ERROR_VARIABLE pip_err)
+ if (NOT pip_res EQUAL 0)
+ message(FATAL_ERROR "pip install aqtinstall failed: ${pip_err}")
+ endif()
+
+ execute_process(COMMAND "${CMAKE_COMMAND}" -E env PYTHONPATH="${aqt_install_path}" "${PYTHON3_EXECUTABLE}" -m aqt ${install_args}
+ WORKING_DIRECTORY "${base_path}"
+ RESULT_VARIABLE aqt_res
+ ERROR_VARIABLE aqt_err)
+ if (NOT aqt_res EQUAL 0)
+ message(FATAL_ERROR "aqt (python) failed: ${aqt_err}")
+ endif()
endif()
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
@@ -210,7 +221,7 @@ endfunction()
function(download_qt target)
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
- get_external_prefix(qt base_path)
+ set(base_path "${CMAKE_BINARY_DIR}/externals/qt")
file(MAKE_DIRECTORY "${base_path}")
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
@@ -227,26 +238,34 @@ function(download_qt target)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()
-function(download_moltenvk)
-set(MOLTENVK_PLATFORM "macOS")
+function(download_moltenvk version platform)
+ if(NOT version)
+ message(FATAL_ERROR "download_moltenvk: version argument is required")
+ endif()
+ if(NOT platform)
+ message(FATAL_ERROR "download_moltenvk: platform argument is required")
+ endif()
-set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
-set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
-if (NOT EXISTS ${MOLTENVK_DIR})
-if (NOT EXISTS ${MOLTENVK_TAR})
- file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.10-rc2/MoltenVK-all.tar
- ${MOLTENVK_TAR} SHOW_PROGRESS)
-endif()
+ set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
+ set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
-execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
- WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
-endif()
+ if(NOT EXISTS "${MOLTENVK_DIR}")
+ if(NOT EXISTS "${MOLTENVK_TAR}")
+ file(DOWNLOAD "https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar"
+ "${MOLTENVK_TAR}" SHOW_PROGRESS)
+ endif()
-# Add the MoltenVK library path to the prefix so find_library can locate it.
-list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${MOLTENVK_PLATFORM}")
-set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
+ execute_process(
+ COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals"
+ RESULT_VARIABLE tar_res
+ ERROR_VARIABLE tar_err
+ )
+ if(NOT tar_res EQUAL 0)
+ message(FATAL_ERROR "Extracting MoltenVK failed: ${tar_err}")
+ endif()
+ endif()
+ list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}")
+ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()
-function(get_external_prefix lib_name prefix_var)
- set(${prefix_var} "${CMAKE_BINARY_DIR}/externals/${lib_name}" PARENT_SCOPE)
-endfunction()
diff --git a/docs/Deps.md b/docs/Deps.md
index cfc6f0365b..573d1fe14a 100644
--- a/docs/Deps.md
+++ b/docs/Deps.md
@@ -4,8 +4,8 @@ To build Eden, you MUST have a C++ compiler.
* On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+
- GCC 12 also requires Clang 14+
* On Windows, this is either:
- - **[MSVC](https://visualstudio.microsoft.com/downloads/)**,
- * *A convenience script to install the **minimal** version (Visual Build Tools) is provided in `.ci/windows/install-msvc.ps1`*
+ - **[MSVC](https://visualstudio.microsoft.com/downloads/)** (you should select *Community* option),
+ * *A convenience script to install the Visual Community Studio 2022 with necessary tools is provided in `.ci/windows/install-msvc.ps1`*
- clang-cl - can be downloaded from the MSVC installer,
- or **[MSYS2](https://www.msys2.org)**
* On macOS, this is Apple Clang
@@ -101,7 +101,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
Ubuntu, Debian, Mint Linux
```sh
-sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libzydis-dev zydis-tools libzycore-dev
+sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2t64 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libzydis-dev zydis-tools libzycore-dev vulkan-utility-libraries-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev
```
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
@@ -211,4 +211,4 @@ Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/l
## All Done
-You may now return to the **[root build guide](Build.md)**.
\ No newline at end of file
+You may now return to the **[root build guide](Build.md)**.
diff --git a/docs/Development.md b/docs/Development.md
index 1ed75a7c23..c223409243 100644
--- a/docs/Development.md
+++ b/docs/Development.md
@@ -36,6 +36,28 @@ Pull requests are only to be merged by core developers when properly tested and
- Maintainers are permitted to change namespaces at will.
- Commits within PRs are not required to be namespaced, but it is highly recommended.
+## Adding new settings
+
+When adding new settings, use `tr("Setting:")` if the setting is meant to be a field, otherwise use `tr("Setting")` if the setting is meant to be a Yes/No or checkmark type of setting, see [this short style guide](https://learn.microsoft.com/en-us/style-guide/punctuation/colons#in-ui).
+
+- The majority of software must work with the default option selected for such setting. Unless the setting significantly degrades performance.
+- Debug settings must never be turned on by default.
+- Provide reasonable bounds (for example, a setting controlling the amount of VRAM should never be 0).
+- The description of the setting must be short and concise, if the setting "does a lot of things" consider splitting the setting into multiple if possible.
+- Try to avoid excessive/redundant explainations "recommended for most users and games" can just be "(recommended)".
+- Try to not write "slow/fast" options unless it clearly degrades/increases performance for a given case, as most options may modify behaviour that result in different metrics accross different systems. If for example the option is an "accuracy" option, writing "High" is sufficient to imply "Slow". No need to write "High (Slow)".
+
+Some examples:
+- "[...] negatively affecting image quality", "[...] degrading image quality": Same wording but with less filler.
+- "[...] this may cause some glitches or crashes in some games", "[...] this may cause soft-crashes": Crashes implies there may be glitches (as crashes are technically a form of a fatal glitch). The entire sentence is structured as "may cause [...] on some games", which is redundant, because "may cause [...] in games" has the same semantic meaning ("may" is a chance that it will occur on "some" given set).
+- "FIFO Relaxed is similar to FIFO [...]", "FIFO Relaxed [...]": The name already implies similarity.
+- "[...] but may also reduce performance in some cases", "[...] but may degrade performance": Again, "some cases" and "may" implies there is a probability.
+- "[...] it can [...] in some cases", "[...] it can [...]": Implied probability.
+
+Before adding a new setting, consider:
+- Does the piece of code that the setting pertains to, make a significant difference if it's on/off?
+- Can it be auto-detected?
+
# IDE setup
## VSCode
diff --git a/docs/Options.md b/docs/Options.md
index d19aab63f6..6af91e4918 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -31,7 +31,7 @@ Notes:
* Currently, build fails without this
- `YUZU_USE_FASTER_LD` (ON) Check if a faster linker is available
* Only available on UNIX
-- `USE_SYSTEM_MOLTENVK` (OFF, macOS only) Use the system MoltenVK lib (instead of the bundled one)
+- `YUZU_APPLE_USE_BUNDLED_MONTENVK` (ON, macOS only) Download bundled MoltenVK lib)
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS)
- `ENABLE_OPENSSL` (ON for Linux and *BSD) Enable OpenSSL backend for the ssl service
* Always enabled if the web service is enabled
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 2046af42c8..da40453497 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
var isActivityRecreated = false
private lateinit var nfcReader: NfcReader
+ private var touchDownTime: Long = 0
+ private val maxTapDuration = 500L
+
private val gyro = FloatArray(3)
private val accel = FloatArray(3)
private var motionTimestamp: Long = 0
@@ -489,6 +492,38 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
+ val emulationFragment = navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
+
+ emulationFragment?.let { fragment ->
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ touchDownTime = System.currentTimeMillis()
+ // show overlay immediately on touch and cancel timer
+ if (!emulationViewModel.drawerOpen.value) {
+ fragment.handler.removeCallbacksAndMessages(null)
+ fragment.showOverlay()
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (!emulationViewModel.drawerOpen.value) {
+ val touchDuration = System.currentTimeMillis() - touchDownTime
+
+ if (touchDuration <= maxTapDuration) {
+ fragment.handleScreenTap(false)
+ } else {
+ // just start the auto-hide timer without toggling visibility
+ fragment.handleScreenTap(true)
+ }
+ }
+ }
+ }
+ }
+
+ return super.dispatchTouchEvent(event)
+ }
+
fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 3c5b9003de..638e1101db 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
FRAME_INTERPOLATION("frame_interpolation"),
// FRAME_SKIPPING("frame_skipping"),
+ ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
+
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 21aad8b5d1..d5556a337b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
LOGIN_SHARE_APPLET("login_share_applet_mode"),
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
- MY_PAGE_APPLET("my_page_applet_mode")
+ MY_PAGE_APPLET("my_page_applet_mode"),
+ INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide")
;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index a52f582031..454d0e4807 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -12,6 +12,7 @@ object Settings {
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
+ SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_INPUT(R.string.preferences_controls),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 1f2ba81a73..5f7f7a43f9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -96,6 +96,7 @@ abstract class SettingsItem(
const val TYPE_INT_SINGLE_CHOICE = 9
const val TYPE_INPUT_PROFILE = 10
const val TYPE_STRING_INPUT = 11
+ const val TYPE_SPINBOX = 12
const val FASTMEM_COMBINED = "fastmem_combined"
@@ -385,6 +386,22 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution
)
)
+ put(
+ SwitchSetting(
+ BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,
+ titleId = R.string.enable_input_overlay_auto_hide,
+ )
+ )
+ put(
+ SpinBoxSetting(
+ IntSetting.INPUT_OVERLAY_AUTO_HIDE,
+ titleId = R.string.overlay_auto_hide,
+ descriptionId = R.string.overlay_auto_hide_description,
+ min = 1,
+ max = 999,
+ valueHint = R.string.seconds
+ )
+ )
put(
SwitchSetting(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt
new file mode 100644
index 0000000000..0b0d01dfe0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
+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.AbstractShortSetting
+
+class SpinBoxSetting(
+ setting: AbstractSetting,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ val valueHint: Int,
+ val min: Int,
+ val max: Int
+) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
+ override val type = TYPE_SPINBOX
+
+ fun getSelectedValue(needsGlobal: Boolean = false) =
+ when (setting) {
+ is AbstractByteSetting -> setting.getByte(needsGlobal).toInt()
+ is AbstractShortSetting -> setting.getShort(needsGlobal).toInt()
+ is AbstractIntSetting -> setting.getInt(needsGlobal)
+ is AbstractFloatSetting -> setting.getFloat(needsGlobal).toInt()
+ else -> 0
+ }
+
+ fun setSelectedValue(value: Int) =
+ when (setting) {
+ is AbstractByteSetting -> setting.setByte(value.toByte())
+ is AbstractShortSetting -> setting.setShort(value.toShort())
+ is AbstractFloatSetting -> setting.setFloat(value.toFloat())
+ else -> (setting as AbstractIntSetting).setInt(value)
+ }
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 500ac6e66e..bdc51b7070 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
@@ -61,6 +61,10 @@ class SettingsAdapter(
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
+ SettingsItem.TYPE_SPINBOX -> {
+ SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this)
+ }
+
SettingsItem.TYPE_SUBMENU -> {
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
@@ -191,6 +195,14 @@ class SettingsAdapter(
position
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
+ fun onSpinBoxClick(item: SpinBoxSetting, position: Int) {
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SPINBOX,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
+ }
fun onSubmenuClick(item: SubmenuSetting) {
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
index dc9f561eca..2a97f15892 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
@@ -14,6 +14,7 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
@@ -22,6 +23,7 @@ import com.google.android.material.slider.Slider
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.databinding.DialogSpinboxBinding
import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
@@ -30,6 +32,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.utils.ParamPackage
@@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private lateinit var sliderBinding: DialogSliderBinding
private lateinit var stringInputBinding: DialogEditTextBinding
+ private lateinit var spinboxBinding: DialogSpinboxBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.create()
}
+ SettingsItem.TYPE_SPINBOX -> {
+ spinboxBinding = DialogSpinboxBinding.inflate(layoutInflater)
+ val item = settingsViewModel.clickedItem as SpinBoxSetting
+
+ val currentValue = item.getSelectedValue()
+ spinboxBinding.editValue.setText(currentValue.toString())
+ spinboxBinding.textInputLayout.hint = getString(item.valueHint)
+
+ val dialog = MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.title)
+ .setView(spinboxBinding.root)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+ .create()
+
+ val updateButtonState = { enabled: Boolean ->
+ dialog.setOnShowListener { dialogInterface ->
+ (dialogInterface as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
+ }
+ if (dialog.isShowing) {
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
+ }
+ }
+
+ val updateValidity = { value: Int ->
+ val isValid = value in item.min..item.max
+ if (isValid) {
+ spinboxBinding.textInputLayout.error = null
+ } else {
+ spinboxBinding.textInputLayout.error = getString(
+ if (value < item.min) R.string.value_too_low else R.string.value_too_high,
+ if (value < item.min) item.min else item.max
+ )
+ }
+ updateButtonState(isValid)
+ }
+
+ spinboxBinding.buttonDecrement.setOnClickListener {
+ val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
+ val newValue = current - 1
+ spinboxBinding.editValue.setText(newValue.toString())
+ updateValidity(newValue)
+ }
+
+ spinboxBinding.buttonIncrement.setOnClickListener {
+ val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
+ val newValue = current + 1
+ spinboxBinding.editValue.setText(newValue.toString())
+ updateValidity(newValue)
+ }
+
+ spinboxBinding.editValue.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ override fun afterTextChanged(s: Editable?) {
+ val value = s.toString().toIntOrNull()
+ if (value != null) {
+ updateValidity(value)
+ } else {
+ spinboxBinding.textInputLayout.error = getString(R.string.invalid_value)
+ updateButtonState(false)
+ }
+ }
+ })
+
+ updateValidity(currentValue)
+
+ dialog
+ }
+
SettingsItem.TYPE_STRING_INPUT -> {
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as StringInputSetting
@@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
}
+ is SpinBoxSetting -> {
+ val spinBoxSetting = settingsViewModel.clickedItem as SpinBoxSetting
+ val value = spinboxBinding.editValue.text.toString().toIntOrNull()
+ if (value != null && value in spinBoxSetting.min..spinBoxSetting.max) {
+ spinBoxSetting.setSelectedValue(value)
+ }
+ }
+
is StringInputSetting -> {
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
stringInputSetting.setSelectedValue(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 14d62ceec3..715baec72f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -97,6 +97,7 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
+ MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
MenuTag.SECTION_INPUT -> addInputSettings(sl)
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
@@ -156,6 +157,14 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_SOC_OVERLAY
)
)
+ add(
+ SubmenuSetting(
+ titleId = R.string.input_overlay_options,
+ iconId = R.drawable.ic_controller,
+ descriptionId = R.string.input_overlay_options_description,
+ menuKey = MenuTag.SECTION_INPUT_OVERLAY
+ )
+ )
}
add(
SubmenuSetting(
@@ -264,6 +273,13 @@ class SettingsFragmentPresenter(
}
}
+ private fun addInputOverlaySettings(sl: ArrayList) {
+ sl.apply {
+ add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
+ add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
+ }
+ }
+
private fun addSocOverlaySettings(sl: ArrayList) {
sl.apply {
add(HeaderSetting(R.string.stats_overlay_customization))
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt
new file mode 100644
index 0000000000..1f9a16b798
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui.viewholder
+
+import android.view.View
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+
+class SpinBoxViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
+ SettingViewHolder(binding.root, adapter) {
+ private lateinit var setting: SpinBoxSetting
+
+ override fun bind(item: SettingsItem) {
+ setting = item as SpinBoxSetting
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(item.description.isNotEmpty())
+ binding.textSettingDescription.text = setting.description
+ binding.textSettingValue.setVisible(true)
+ binding.textSettingValue.text = setting.getSelectedValue().toString()
+
+ binding.buttonClear.setVisible(setting.clearable)
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
+ }
+
+ setStyle(setting.isEditable, binding)
+ }
+ override fun onClick(clicked: View) {
+ if (setting.isEditable) {
+ adapter.onSpinBoxClick(setting, bindingAdapterPosition)
+ }
+ }
+ override fun onLongClick(clicked: View): Boolean {
+ if (setting.isEditable) {
+ return adapter.onLongClick(setting, bindingAdapterPosition)
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 8162098caa..b2d6135372 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsUpdater: (() -> Unit)? = null
private var socUpdater: (() -> Unit)? = null
+ val handler = Handler(Looper.getMainLooper())
+ private var isOverlayVisible = true
+
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
@@ -452,7 +455,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
/**
* Ask user if they want to launch with default settings when custom settings fail
*/
- private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean {
+ private suspend fun askUserToLaunchWithDefaultSettings(
+ gameTitle: String,
+ errorMessage: String
+ ): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
MaterialAlertDialogBuilder(requireContext())
@@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updateShowStatsOverlay()
updateSocOverlay()
+ initializeOverlayAutoHide()
+
// Re update binding when the specs values get initialized properly
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById(R.id.text_game_title)
@@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updatePauseMenuEntry(emulationState.isPaused)
}
}
+
+ // if the overlay auto-hide setting is changed while paused,
+ // we need to reinitialize the auto-hide timer
+ initializeOverlayAutoHide()
+
}
private fun resetInputOverlay() {
@@ -924,6 +937,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
IntSetting.OVERLAY_OPACITY.reset()
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
+ binding.surfaceInputOverlay.resetIndividualControlScale()
}
}
@@ -1034,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
- status == BatteryManager.BATTERY_STATUS_FULL
+ status == BatteryManager.BATTERY_STATUS_FULL
if (isCharging) {
sb.append(" ${getString(R.string.charging)}")
@@ -1546,6 +1560,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
setControlScale(50)
setControlOpacity(100)
+ binding.surfaceInputOverlay.resetIndividualControlScale()
}
.show()
}
@@ -1726,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val socUpdateHandler = Handler(Looper.myLooper()!!)
}
-}
\ No newline at end of file
+
+ private fun startOverlayAutoHideTimer(seconds: Int) {
+ handler.removeCallbacksAndMessages(null)
+
+ handler.postDelayed({
+ if (isOverlayVisible) {
+ hideOverlay()
+ }
+ }, seconds * 1000L)
+ }
+
+ fun handleScreenTap(isLongTap: Boolean) {
+ val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
+ val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
+
+ if (!shouldProceed) {
+ return
+ }
+
+ // failsafe
+ if (autoHideSeconds == 0) {
+ showOverlay()
+ return
+ }
+
+ if (!isOverlayVisible && !isLongTap) {
+ showOverlay()
+ }
+
+ startOverlayAutoHideTimer(autoHideSeconds)
+ }
+
+ private fun initializeOverlayAutoHide() {
+ val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
+ val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
+ val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
+
+ if (autoHideEnabled && showOverlay) {
+ showOverlay()
+ startOverlayAutoHideTimer(autoHideSeconds)
+ }
+ }
+
+
+ fun showOverlay() {
+ if (!isOverlayVisible) {
+ isOverlayVisible = true
+ ViewUtils.showView(binding.surfaceInputOverlay, 500)
+ }
+ }
+
+ private fun hideOverlay() {
+ if (isOverlayVisible) {
+ isOverlayVisible = false
+ ViewUtils.hideView(binding.surfaceInputOverlay, 500)
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index 737e035840..9f050a5053 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -13,6 +13,8 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable
import android.os.Build
+import android.os.Handler
+import android.os.Looper
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
+ private var scaleDialog: OverlayScaleDialog? = null
+ private var touchStartX = 0f
+ private var touchStartY = 0f
+ private var hasMoved = false
+ private val moveThreshold = 20f
+
private lateinit var windowInsets: WindowInsets
var layout = OverlayLayout.Landscape
@@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
buttonBeingConfigured = button
buttonBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
- buttonBeingConfigured!!.onConfigureTouch(event)
- invalidate()
- return true
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ buttonBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ return true
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
- // Persist button position by saving new place.
- saveControlPosition(
- buttonBeingConfigured!!.overlayControlData.id,
- buttonBeingConfigured!!.bounds.centerX(),
- buttonBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ showScaleDialog(
+ buttonBeingConfigured,
+ null,
+ null,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ saveControlPosition(
+ buttonBeingConfigured!!.overlayControlData.id,
+ buttonBeingConfigured!!.bounds.centerX(),
+ buttonBeingConfigured!!.bounds.centerY(),
+ individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale,
+ layout
+ )
+ }
buttonBeingConfigured = null
}
}
@@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
dpadBeingConfigured = dpad
dpadBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
- dpadBeingConfigured!!.onConfigureTouch(event)
- invalidate()
- return true
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ dpadBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ return true
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
- // Persist button position by saving new place.
- saveControlPosition(
- OverlayControl.COMBINED_DPAD.id,
- dpadBeingConfigured!!.bounds.centerX(),
- dpadBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ // This was a click, show scale dialog for dpad
+ showScaleDialog(
+ null,
+ dpadBeingConfigured,
+ null,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ // This was a move, save position
+ saveControlPosition(
+ OverlayControl.COMBINED_DPAD.id,
+ dpadBeingConfigured!!.bounds.centerX(),
+ dpadBeingConfigured!!.bounds.centerY(),
+ individuaScale = dpadBeingConfigured!!.individualScale,
+ layout
+ )
+ }
dpadBeingConfigured = null
}
}
@@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
joystickBeingConfigured = joystick
joystickBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
- joystickBeingConfigured!!.onConfigureTouch(event)
- invalidate()
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ joystickBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
- saveControlPosition(
- joystickBeingConfigured!!.prefId,
- joystickBeingConfigured!!.bounds.centerX(),
- joystickBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ showScaleDialog(
+ null,
+ null,
+ joystickBeingConfigured,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ saveControlPosition(
+ joystickBeingConfigured!!.prefId,
+ joystickBeingConfigured!!.bounds.centerX(),
+ joystickBeingConfigured!!.bounds.centerY(),
+ individuaScale = joystickBeingConfigured!!.individualScale,
+ layout
+ )
+ }
joystickBeingConfigured = null
}
}
@@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
invalidate()
}
- private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) {
+ private fun saveControlPosition(
+ id: String,
+ x: Int,
+ y: Int,
+ individuaScale: Float,
+ layout: OverlayLayout
+ ) {
val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
val min = windowSize.first
val max = windowSize.second
val overlayControlData = NativeConfig.getOverlayControlData()
val data = overlayControlData.firstOrNull { it.id == id }
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
+
when (layout) {
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
+
}
+
+ data?.individualScale = individuaScale
+
NativeConfig.setOverlayControlData(overlayControlData)
}
fun setIsInEditMode(editMode: Boolean) {
inEditMode = editMode
+ if (!editMode) {
+ scaleDialog?.dismiss()
+ scaleDialog = null
+ }
}
+ private fun showScaleDialog(
+ button: InputOverlayDrawableButton?,
+ dpad: InputOverlayDrawableDpad?,
+ joystick: InputOverlayDrawableJoystick?,
+ x: Int, y: Int
+ ) {
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ // prevent dialog from being spam opened
+ scaleDialog?.dismiss()
+
+
+ when {
+ button != null -> {
+ val buttonData =
+ overlayControlData.firstOrNull { it.id == button.overlayControlData.id }
+ if (buttonData != null) {
+ scaleDialog =
+ OverlayScaleDialog(context, button.overlayControlData) { newScale ->
+ saveControlPosition(
+ button.overlayControlData.id,
+ button.bounds.centerX(),
+ button.bounds.centerY(),
+ individuaScale = newScale,
+ layout
+ )
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, button.bounds.width(), button.bounds.height())
+
+ }
+ }
+
+ dpad != null -> {
+ val dpadData =
+ overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
+ if (dpadData != null) {
+ scaleDialog = OverlayScaleDialog(context, dpadData) { newScale ->
+ saveControlPosition(
+ OverlayControl.COMBINED_DPAD.id,
+ dpad.bounds.centerX(),
+ dpad.bounds.centerY(),
+ newScale,
+ layout
+ )
+
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, dpad.bounds.width(), dpad.bounds.height())
+
+ }
+ }
+
+ joystick != null -> {
+ val joystickData = overlayControlData.firstOrNull { it.id == joystick.prefId }
+ if (joystickData != null) {
+ scaleDialog = OverlayScaleDialog(context, joystickData) { newScale ->
+ saveControlPosition(
+ joystick.prefId,
+ joystick.bounds.centerX(),
+ joystick.bounds.centerY(),
+ individuaScale = newScale,
+ layout
+ )
+
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, joystick.bounds.width(), joystick.bounds.height())
+
+ }
+ }
+ }
+ }
+
+
/**
* Applies and saves all default values for the overlay
*/
@@ -664,12 +830,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val overlayControlData = NativeConfig.getOverlayControlData()
overlayControlData.forEach {
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
+ it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!!
}
NativeConfig.setOverlayControlData(overlayControlData)
refreshControls()
}
+ fun resetIndividualControlScale() {
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ overlayControlData.forEach { data ->
+ val defaultControlData = OverlayControl.from(data.id) ?: return@forEach
+ data.individualScale = defaultControlData.defaultIndividualScaleResource
+ }
+ NativeConfig.setOverlayControlData(overlayControlData)
+ NativeConfig.saveGlobalConfig()
+ refreshControls()
+ }
+
private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
val overlayControlData = NativeConfig.getOverlayControlData()
for (data in overlayControlData) {
@@ -860,6 +1038,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ scale *= overlayControlData.individualScale
+
// Initialize the InputOverlayDrawableButton.
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
val pressedStateBitmap = getBitmap(context, pressedResId, scale)
@@ -922,11 +1103,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// Resources handle for fetching the initial Drawable resource.
val res = context.resources
+ // Get the dpad control data for individual scale
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ val dpadData = overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
+
// Decide scale based on button ID and user preference
var scale = 0.25f
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ if (dpadData != null) {
+ scale *= dpadData.individualScale
+ }
+
// Initialize the InputOverlayDrawableDpad.
val defaultStateBitmap =
getBitmap(context, defaultResId, scale)
@@ -1000,6 +1190,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ scale *= overlayControlData.individualScale
+
// Initialize the InputOverlayDrawableJoystick.
val bitmapOuter = getBitmap(context, resOuter, scale)
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
index 0cb6ff2440..01f07e4f36 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -42,6 +42,8 @@ class InputOverlayDrawableDpad(
val width: Int
val height: Int
+ var individualScale: Float = 1.0f
+
private val defaultStateBitmap: BitmapDrawable
private val pressedOneDirectionStateBitmap: BitmapDrawable
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index 4b07107fca..bc3ff15b21 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick(
val width: Int
val height: Int
+ var individualScale: Float = 1.0f
+
private var opacity: Int = 0
private var virtBounds: Rect
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt
new file mode 100644
index 0000000000..f489ef3b7c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.overlay
+
+import android.app.Dialog
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.WindowManager
+import android.widget.TextView
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.slider.Slider
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+
+class OverlayScaleDialog(
+ context: Context,
+ private val overlayControlData: OverlayControlData,
+ private val onScaleChanged: (Float) -> Unit
+) : Dialog(context) {
+
+ private var currentScale = overlayControlData.individualScale
+ private val originalScale = overlayControlData.individualScale
+ private lateinit var scaleValueText: TextView
+ private lateinit var scaleSlider: Slider
+
+ init {
+ setupDialog()
+ }
+
+ private fun setupDialog() {
+ val view = LayoutInflater.from(context).inflate(R.layout.dialog_overlay_scale, null)
+ setContentView(view)
+
+ window?.setBackgroundDrawable(null)
+
+ window?.apply {
+ attributes = attributes.apply {
+ flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv()
+ flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ }
+ }
+
+ scaleValueText = view.findViewById(R.id.scaleValueText)
+ scaleSlider = view.findViewById(R.id.scaleSlider)
+ val resetButton = view.findViewById(R.id.resetButton)
+ val confirmButton = view.findViewById(R.id.confirmButton)
+ val cancelButton = view.findViewById(R.id.cancelButton)
+
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ scaleSlider.value = currentScale
+
+ scaleSlider.addOnChangeListener { _, value, input ->
+ if (input) {
+ currentScale = value
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ }
+ }
+
+ scaleSlider.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
+ override fun onStartTrackingTouch(slider: Slider) {
+ // pass
+ }
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ onScaleChanged(currentScale)
+ }
+ })
+
+ resetButton.setOnClickListener {
+ currentScale = 1.0f
+ scaleSlider.value = 1.0f
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ onScaleChanged(currentScale)
+ }
+
+ confirmButton.setOnClickListener {
+ overlayControlData.individualScale = currentScale
+ //slider value is already saved on touch dispatch but just to be sure
+ onScaleChanged(currentScale)
+ dismiss()
+ }
+
+ // both cancel button and back gesture should revert the scale change
+ cancelButton.setOnClickListener {
+ onScaleChanged(originalScale)
+ dismiss()
+ }
+
+ setOnCancelListener {
+ onScaleChanged(originalScale)
+ dismiss()
+ }
+ }
+
+ fun showDialog(anchorX: Int, anchorY: Int, anchorHeight: Int, anchorWidth: Int) {
+ show()
+
+ show()
+
+ // TODO: this calculation is a bit rough, improve it later on
+ window?.let { window ->
+ val layoutParams = window.attributes
+ layoutParams.gravity = Gravity.TOP or Gravity.START
+
+ val density = context.resources.displayMetrics.density
+ val dialogWidthPx = (320 * density).toInt()
+ val dialogHeightPx = (400 * density).toInt() // set your estimated dialog height
+
+ val screenHeight = context.resources.displayMetrics.heightPixels
+
+
+ layoutParams.x = anchorX + anchorWidth / 2 - dialogWidthPx / 2
+ layoutParams.y = anchorY + anchorHeight / 2 - dialogHeightPx / 2
+ layoutParams.width = dialogWidthPx
+
+
+ window.attributes = layoutParams
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
index a0eeadf4bc..10cc547d0b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model
@@ -12,126 +12,144 @@ enum class OverlayControl(
val defaultVisibility: Boolean,
@IntegerRes val defaultLandscapePositionResources: Pair,
@IntegerRes val defaultPortraitPositionResources: Pair,
- @IntegerRes val defaultFoldablePositionResources: Pair
+ @IntegerRes val defaultFoldablePositionResources: Pair,
+ val defaultIndividualScaleResource: Float,
) {
BUTTON_A(
"button_a",
true,
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
- Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE),
+ 1.0f
),
BUTTON_B(
"button_b",
true,
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
- Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE),
+ 1.0f
),
BUTTON_X(
"button_x",
true,
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
- Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE),
+ 1.0f
),
BUTTON_Y(
"button_y",
true,
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
- Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE),
+ 1.0f
),
BUTTON_PLUS(
"button_plus",
true,
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
- Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE),
+ 1.0f
),
BUTTON_MINUS(
"button_minus",
true,
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
- Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE),
+ 1.0f
),
BUTTON_HOME(
"button_home",
false,
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
- Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE),
+ 1.0f
),
BUTTON_CAPTURE(
"button_capture",
false,
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
- Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE),
+ 1.0f
),
BUTTON_L(
"button_l",
true,
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
- Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE),
+ 1.0f
),
BUTTON_R(
"button_r",
true,
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
- Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE),
+ 1.0f
),
BUTTON_ZL(
"button_zl",
true,
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
- Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE),
+ 1.0f
),
BUTTON_ZR(
"button_zr",
true,
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
- Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE),
+ 1.0f
),
BUTTON_STICK_L(
"button_stick_l",
true,
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
- Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE),
+ 1.0f
),
BUTTON_STICK_R(
"button_stick_r",
true,
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
- Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE),
+ 1.0f
),
STICK_L(
"stick_l",
true,
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
- Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
+ Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE),
+ 1.0f
),
STICK_R(
"stick_r",
true,
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
- Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
+ Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE),
+ 1.0f
),
COMBINED_DPAD(
"combined_dpad",
true,
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
- Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
+ Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE),
+ 1.0f
);
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair {
@@ -173,7 +191,8 @@ enum class OverlayControl(
defaultVisibility,
getDefaultPositionForLayout(OverlayLayout.Landscape),
getDefaultPositionForLayout(OverlayLayout.Portrait),
- getDefaultPositionForLayout(OverlayLayout.Foldable)
+ getDefaultPositionForLayout(OverlayLayout.Foldable),
+ defaultIndividualScaleResource
)
companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
index 26cfeb1db5..6cc5a59c98 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model
@@ -8,7 +8,8 @@ data class OverlayControlData(
var enabled: Boolean,
var landscapePosition: Pair,
var portraitPosition: Pair,
- var foldablePosition: Pair
+ var foldablePosition: Pair,
+ var individualScale: Float
) {
fun positionFromLayout(layout: OverlayLayout): Pair =
when (layout) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index de0794a17f..5325f688b6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -170,7 +170,8 @@ object DirectoryInitialization {
buttonEnabled,
Pair(landscapeXPosition, landscapeYPosition),
Pair(portraitXPosition, portraitYPosition),
- Pair(foldableXPosition, foldableYPosition)
+ Pair(foldableXPosition, foldableYPosition),
+ OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f
)
overlayControlDataMap[buttonId] = controlData
setOverlayData = true
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index a79a64afbb..41ac680d6b 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include
#include
@@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() {
ReadDoubleSetting(std::string("foldable\\x_position"));
control_data.foldable_position.second =
ReadDoubleSetting(std::string("foldable\\y_position"));
+ control_data.individual_scale = static_cast(ReadDoubleSetting(std::string("individual_scale")));
AndroidSettings::values.overlay_control_data.push_back(control_data);
}
EndArray();
@@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() {
control_data.foldable_position.first);
WriteDoubleSetting(std::string("foldable\\y_position"),
control_data.foldable_position.second);
+ WriteDoubleSetting(std::string("individual_scale"), static_cast(control_data.individual_scale));
}
EndArray();
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index cd18f1e5b3..c9e59ce105 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -24,6 +24,7 @@ namespace AndroidSettings {
std::pair landscape_position;
std::pair portrait_position;
std::pair foldable_position;
+ float individual_scale;
};
struct Values {
@@ -79,6 +80,15 @@ namespace AndroidSettings {
Settings::Category::Overlay,
Settings::Specialization::Paired, true,
true};
+ Settings::Setting enable_input_overlay_auto_hide{linkage, false,
+ "enable_input_overlay_auto_hide",
+ Settings::Category::Overlay,
+ Settings::Specialization::Default, true,
+ true,};
+
+ Settings::Setting input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
+ Settings::Category::Overlay,
+ Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting perf_overlay_background{linkage, false, "perf_overlay_background",
Settings::Category::Overlay,
Settings::Specialization::Default, true,
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 0b26280c6c..e6021ed217 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include
@@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN
env->NewObject(Common::Android::GetOverlayControlDataClass(),
Common::Android::GetOverlayControlDataConstructor(),
Common::Android::ToJString(env, control_data.id), control_data.enabled,
- jlandscapePosition, jportraitPosition, jfoldablePosition);
+ jlandscapePosition, jportraitPosition, jfoldablePosition,
+ control_data.individual_scale);
+
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
}
return joverlayControlDataArray;
@@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
env,
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
+ float individual_scale = static_cast(env->GetFloatField(
+ joverlayControlData, Common::Android::GetOverlayControlDataIndividualScaleField()));
+
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
Common::Android::GetJString(env, jidString), enabled, landscape_position,
- portrait_position, foldable_position});
+ portrait_position, foldable_position, individual_scale});
}
}
diff --git a/src/android/app/src/main/res/layout/dialog_overlay_scale.xml b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml
new file mode 100644
index 0000000000..9e047f211e
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/dialog_spinbox.xml b/src/android/app/src/main/res/layout/dialog_spinbox.xml
new file mode 100644
index 0000000000..2f18ab750d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_spinbox.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 08ca53ad81..1b66c191d3 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -251,7 +251,9 @@
- @string/scaling_filter_nearest_neighbor
- @string/scaling_filter_bilinear
- @string/scaling_filter_bicubic
+ - @string/scaling_filter_spline1
- @string/scaling_filter_gaussian
+ - @string/scaling_filter_lanczos
- @string/scaling_filter_scale_force
- @string/scaling_filter_fsr
- @string/scaling_filter_area
@@ -265,6 +267,8 @@
- 4
- 5
- 6
+ - 7
+ - 8
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 48ea3ed99b..2a5cc48bb1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -12,6 +12,24 @@
Eden
Eden Switch emulator notifications
Eden is Running
+ Seconds
+
+
+ Increment
+ Decrement
+ Value
+ Value must be at least %1$d
+ Value must be at most %1$d
+ Invalid value
+
+
+
+ Overlay Auto Hide
+ Automatically hide the touch controls overlay after the specified time of inactivity.
+ Enable Overlay Auto Hide
+
+ Input Overlay
+ Configure on-screen controls
@@ -855,6 +873,7 @@
Touchscreen
Lock drawer
Unlock drawer
+ Reset
Loading settings…
@@ -865,12 +884,12 @@
Abort
Continue
System Archive Not Found
- %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.
+ %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes.
A system archive
Save/Load Error
Fatal Error
- A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.
- Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.
+ A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes.
+ Turning off this setting will significantly degrade performance. It"s recommended that you leave this setting enabled.
Device RAM: %1$s\nRecommended: %2$s
%1$s %2$s
No bootable game present!
@@ -941,12 +960,12 @@
Normal
High
- Extreme (Slow)
+ Extreme
Default
- Unsafe (fast)
- Safe (stable)
+ Unsafe
+ Safe
ASTC Decoding Method
@@ -992,7 +1011,9 @@
Nearest Neighbor
Bilinear
Bicubic
+ Spline-1
Gaussian
+ Lanczos
ScaleForce
AMD FidelityFX™ Super Resolution
Area
diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp
index e0edd006a5..1198833996 100644
--- a/src/common/android/id_cache.cpp
+++ b/src/common/android/id_cache.cpp
@@ -1,7 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-// SPDX-FileCopyrightText: 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include
@@ -49,6 +46,7 @@ static jclass s_overlay_control_data_class;
static jmethodID s_overlay_control_data_constructor;
static jfieldID s_overlay_control_data_id_field;
static jfieldID s_overlay_control_data_enabled_field;
+static jfieldID s_overlay_control_data_individual_scale_field;
static jfieldID s_overlay_control_data_landscape_position_field;
static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field;
@@ -244,6 +242,10 @@ namespace Common::Android {
return s_overlay_control_data_enabled_field;
}
+ jfieldID GetOverlayControlDataIndividualScaleField() {
+ return s_overlay_control_data_individual_scale_field;
+ }
+
jfieldID GetOverlayControlDataLandscapePositionField() {
return s_overlay_control_data_landscape_position_field;
}
@@ -494,7 +496,7 @@ namespace Common::Android {
reinterpret_cast(env->NewGlobalRef(overlay_control_data_class));
s_overlay_control_data_constructor =
env->GetMethodID(overlay_control_data_class, "",
- "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
+ "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
s_overlay_control_data_id_field =
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
s_overlay_control_data_enabled_field =
@@ -505,6 +507,8 @@ namespace Common::Android {
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
s_overlay_control_data_foldable_position_field =
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
+ s_overlay_control_data_individual_scale_field =
+ env->GetFieldID(overlay_control_data_class, "individualScale", "F");
env->DeleteLocalRef(overlay_control_data_class);
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h
index c56ffcf5c6..6b48f471b7 100644
--- a/src/common/android/id_cache.h
+++ b/src/common/android/id_cache.h
@@ -1,7 +1,4 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// SPDX-FileCopyrightText: 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -67,6 +64,7 @@ jclass GetOverlayControlDataClass();
jmethodID GetOverlayControlDataConstructor();
jfieldID GetOverlayControlDataIdField();
jfieldID GetOverlayControlDataEnabledField();
+jfieldID GetOverlayControlDataIndividualScaleField();
jfieldID GetOverlayControlDataLandscapePositionField();
jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField();
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
index b0b25eb432..722ba41949 100644
--- a/src/common/fs/file.cpp
+++ b/src/common/fs/file.cpp
@@ -3,6 +3,7 @@
#include
+#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#ifdef ANDROID
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 318f311891..a095e0c239 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include "common/assert.h"
#include "common/fs/fs.h"
#ifdef ANDROID
#include "common/fs/fs_android.h"
diff --git a/src/common/settings.h b/src/common/settings.h
index 8605445837..891bde608c 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -178,7 +178,7 @@ struct Values {
SwitchableSetting audio_input_device_id{
linkage, "auto", "input_device", Category::Audio, Specialization::RuntimeList};
SwitchableSetting sound_index{
- linkage, AudioMode::Stereo, AudioMode::Mono, AudioMode::Surround,
+ linkage, AudioMode::Stereo,
"sound_index", Category::SystemAudio, Specialization::Default, true,
true};
SwitchableSetting volume{linkage,
@@ -199,8 +199,6 @@ struct Values {
SwitchableSetting use_multi_core{linkage, true, "use_multi_core", Category::Core};
SwitchableSetting memory_layout_mode{linkage,
MemoryLayout::Memory_4Gb,
- MemoryLayout::Memory_4Gb,
- MemoryLayout::Memory_12Gb,
"memory_layout_mode",
Category::Core,
Specialization::Default,
@@ -240,9 +238,8 @@ struct Values {
#endif
"cpu_backend",
Category::Cpu};
- SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto,
- CpuAccuracy::Auto, CpuAccuracy::Paranoid,
- "cpu_accuracy", Category::Cpu};
+ SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto,
+ "cpu_accuracy", Category::Cpu};
SwitchableSetting use_fast_cpu_time{linkage,
false,
@@ -324,10 +321,10 @@ struct Values {
// Renderer
SwitchableSetting renderer_backend{
- linkage, RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null,
+ linkage, RendererBackend::Vulkan,
"backend", Category::Renderer};
SwitchableSetting shader_backend{
- linkage, ShaderBackend::SpirV, ShaderBackend::Glsl, ShaderBackend::SpirV,
+ linkage, ShaderBackend::SpirV,
"shader_backend", Category::Renderer, Specialization::RuntimeList};
SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
Specialization::RuntimeList};
@@ -342,8 +339,6 @@ struct Values {
Category::Renderer};
SwitchableSetting optimize_spirv_output{linkage,
SpirvOptimizeMode::Never,
- SpirvOptimizeMode::Never,
- SpirvOptimizeMode::Always,
"optimize_spirv_output",
Category::Renderer};
SwitchableSetting use_asynchronous_gpu_emulation{
@@ -354,12 +349,10 @@ struct Values {
#else
AstcDecodeMode::Gpu,
#endif
- AstcDecodeMode::Cpu,
- AstcDecodeMode::CpuAsynchronous,
"accelerate_astc",
Category::Renderer};
SwitchableSetting vsync_mode{
- linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
+ linkage, VSyncMode::Fifo,
"use_vsync", Category::Renderer, Specialization::RuntimeList, true,
true};
SwitchableSetting nvdec_emulation{linkage, NvdecEmulation::Gpu,
@@ -372,8 +365,6 @@ struct Values {
#else
FullscreenMode::Exclusive,
#endif
- FullscreenMode::Borderless,
- FullscreenMode::Exclusive,
"fullscreen_mode",
Category::Renderer,
Specialization::Default,
@@ -381,8 +372,6 @@ struct Values {
true};
SwitchableSetting aspect_ratio{linkage,
AspectRatio::R16_9,
- AspectRatio::R16_9,
- AspectRatio::Stretch,
"aspect_ratio",
Category::Renderer,
Specialization::Default,
@@ -430,8 +419,6 @@ struct Values {
#else
GpuAccuracy::High,
#endif
- GpuAccuracy::Normal,
- GpuAccuracy::Extreme,
"gpu_accuracy",
Category::RendererAdvanced,
Specialization::Default,
@@ -442,8 +429,6 @@ struct Values {
SwitchableSetting dma_accuracy{linkage,
DmaAccuracy::Default,
- DmaAccuracy::Default,
- DmaAccuracy::Safe,
"dma_accuracy",
Category::RendererAdvanced,
Specialization::Default,
@@ -456,20 +441,14 @@ struct Values {
#else
AnisotropyMode::Automatic,
#endif
- AnisotropyMode::Automatic,
- AnisotropyMode::X16,
"max_anisotropy",
Category::RendererAdvanced};
SwitchableSetting astc_recompression{linkage,
AstcRecompression::Uncompressed,
- AstcRecompression::Uncompressed,
- AstcRecompression::Bc3,
"astc_recompression",
Category::RendererAdvanced};
SwitchableSetting vram_usage_mode{linkage,
VramUsageMode::Conservative,
- VramUsageMode::Conservative,
- VramUsageMode::Aggressive,
"vram_usage_mode",
Category::RendererAdvanced};
SwitchableSetting skip_cpu_inner_invalidation{linkage,
@@ -595,14 +574,10 @@ struct Values {
// System
SwitchableSetting language_index{linkage,
Language::EnglishAmerican,
- Language::Japanese,
- Language::Serbian,
"language_index",
Category::System};
- SwitchableSetting region_index{linkage, Region::Usa, Region::Japan,
- Region::Taiwan, "region_index", Category::System};
- SwitchableSetting time_zone_index{linkage, TimeZone::Auto,
- TimeZone::Auto, TimeZone::Zulu,
+ SwitchableSetting region_index{linkage, Region::Usa, "region_index", Category::System};
+ SwitchableSetting time_zone_index{linkage, TimeZone::Auto,
"time_zone_index", Category::System};
// Measured in seconds since epoch
SwitchableSetting custom_rtc_enabled{
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index ebfa4ceb9e..0e5a08d845 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -10,6 +10,7 @@
#pragma once
#include
+#include
#include
#include
#include "common/common_types.h"
@@ -18,8 +19,10 @@ namespace Settings {
template
struct EnumMetadata {
- static std::vector> Canonicalizations();
+ static std::vector> Canonicalizations();
static u32 Index();
+ static constexpr T GetFirst();
+ static constexpr T GetLast();
};
#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
@@ -69,138 +72,101 @@ struct EnumMetadata {
#define PAIR_1(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_2(N, __VA_ARGS__))
#define PAIR(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_1(N, __VA_ARGS__))
-#define ENUM(NAME, ...) \
- enum class NAME : u32 { __VA_ARGS__ }; \
- template <> \
- inline std::vector> EnumMetadata::Canonicalizations() { \
- return {PAIR(NAME, __VA_ARGS__)}; \
- } \
- template <> \
- inline u32 EnumMetadata::Index() { \
- return __COUNTER__; \
+#define PP_HEAD(A, ...) A
+
+#define ENUM(NAME, ...) \
+ enum class NAME : u32 { __VA_ARGS__ }; \
+ template<> inline std::vector> EnumMetadata::Canonicalizations() { \
+ return {PAIR(NAME, __VA_ARGS__)}; \
+ } \
+ template<> inline u32 EnumMetadata::Index() { \
+ return __COUNTER__; \
+ } \
+ template<> inline constexpr NAME EnumMetadata::GetFirst() { \
+ return NAME::PP_HEAD(__VA_ARGS__); \
+ } \
+ template<> inline constexpr NAME EnumMetadata::GetLast() { \
+ return (std::vector>{PAIR(NAME, __VA_ARGS__)}).back().second; \
}
// AudioEngine must be specified discretely due to having existing but slightly different
// canonicalizations
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
-enum class AudioEngine : u32 {
- Auto,
- Cubeb,
- Sdl2,
- Null,
- Oboe,
-};
-
-template <>
-inline std::vector>
-EnumMetadata::Canonicalizations() {
+enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, };
+template<>
+inline std::vector> EnumMetadata::Canonicalizations() {
return {
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
};
}
-
-template <>
+/// @brief This is just a sufficiently large number that is more than the number of other enums declared here
+template<>
inline u32 EnumMetadata::Index() {
- // This is just a sufficiently large number that is more than the number of other enums declared
- // here
return 100;
}
+template<>
+inline constexpr AudioEngine EnumMetadata::GetFirst() {
+ return AudioEngine::Auto;
+}
+template<>
+inline constexpr AudioEngine EnumMetadata::GetLast() {
+ return AudioEngine::Oboe;
+}
ENUM(AudioMode, Mono, Stereo, Surround);
+static_assert(EnumMetadata::GetFirst() == AudioMode::Mono);
+static_assert(EnumMetadata::GetLast() == AudioMode::Surround);
ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch,
Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin,
ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian);
-
ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan);
-
ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt,
- GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
- Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
- Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
-
+ GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
+ Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
+ Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
-
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
-
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
-
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
-
ENUM(VramUsageMode, Conservative, Aggressive);
-
ENUM(RendererBackend, OpenGL, Vulkan, Null);
-
ENUM(ShaderBackend, Glsl, Glasm, SpirV);
-
ENUM(GpuAccuracy, Normal, High, Extreme);
-
ENUM(DmaAccuracy, Default, Unsafe, Safe);
-
ENUM(CpuBackend, Dynarmic, Nce);
-
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
-
ENUM(CpuClock, Boost, Fast)
-
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
-
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
-
ENUM(FullscreenMode, Borderless, Exclusive);
-
ENUM(NvdecEmulation, Off, Cpu, Gpu);
-
-ENUM(ResolutionSetup,
- Res1_4X,
- Res1_2X,
- Res3_4X,
- Res1X,
- Res3_2X,
- Res2X,
- Res3X,
- Res4X,
- Res5X,
- Res6X,
- Res7X,
- Res8X);
-
+ENUM(ResolutionSetup, Res1_4X, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X, Res8X);
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, MaxEnum);
-
ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
-
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
-
ENUM(ConsoleMode, Handheld, Docked);
-
ENUM(AppletMode, HLE, LLE);
-
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
-
ENUM(GpuOverclock, Low, Medium, High)
-
ENUM(TemperatureUnits, Celsius, Fahrenheit)
template
-inline std::string CanonicalizeEnum(Type id) {
+inline std::string_view CanonicalizeEnum(Type id) {
const auto group = EnumMetadata::Canonicalizations();
- for (auto& [name, value] : group) {
- if (value == id) {
+ for (auto& [name, value] : group)
+ if (value == id)
return name;
- }
- }
return "unknown";
}
template
inline Type ToEnum(const std::string& canonicalization) {
const auto group = EnumMetadata::Canonicalizations();
- for (auto& [name, value] : group) {
- if (name == canonicalization) {
+ for (auto& [name, value] : group)
+ if (name == canonicalization)
return value;
- }
- }
return {};
}
} // namespace Settings
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index 0aba2e11c9..a7e6bb6168 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -72,10 +72,17 @@ public:
u32 specialization_ = Specialization::Default, bool save_ = true,
bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
requires(ranged)
- : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_,
- other_setting_),
+ : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_, other_setting_),
value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val} {}
+ explicit Setting(Linkage& linkage, const Type& default_val,
+ const std::string& name, Category category_,
+ u32 specialization_ = Specialization::Default, bool save_ = true,
+ bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
+ requires(ranged && std::is_enum_v)
+ : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_, other_setting_),
+ value{default_val}, default_value{default_val}, maximum{EnumMetadata::GetLast()}, minimum{EnumMetadata::GetFirst()} {}
+
/**
* Returns a reference to the setting's value.
*
@@ -119,9 +126,6 @@ protected:
return value_.has_value() ? std::to_string(*value_) : "none";
} else if constexpr (std::is_same_v) {
return value_ ? "true" : "false";
- } else if constexpr (std::is_same_v) {
- // Compatibility with old AudioEngine setting being a string
- return CanonicalizeEnum(value_);
} else if constexpr (std::is_floating_point_v) {
return fmt::format("{:f}", value_);
} else if constexpr (std::is_enum_v) {
@@ -207,7 +211,7 @@ public:
[[nodiscard]] std::string Canonicalize() const override final {
if constexpr (std::is_enum_v) {
- return CanonicalizeEnum(this->GetValue());
+ return std::string{CanonicalizeEnum(this->GetValue())};
} else {
return ToString(this->GetValue());
}
@@ -288,41 +292,32 @@ public:
* @param other_setting_ A second Setting to associate to this one in metadata
*/
template
- explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name,
- Category category_, u32 specialization_ = Specialization::Default,
- bool save_ = true, bool runtime_modifiable_ = false,
- typename std::enable_if::type other_setting_ = nullptr)
- : Setting{
- linkage, default_val, name, category_, specialization_,
- save_, runtime_modifiable_, other_setting_} {
+ explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(!ranged)
+ : Setting{ linkage, default_val, name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
}
virtual ~SwitchableSetting() = default;
- /**
- * Sets a default value, minimum value, maximum value, and label.
- *
- * @param linkage Setting registry
- * @param default_val Initial value of the setting, and default value of the setting
- * @param min_val Sets the minimum allowed value of the setting
- * @param max_val Sets the maximum allowed value of the setting
- * @param name Label for the setting
- * @param category_ Category of the setting AKA INI group
- * @param specialization_ Suggestion for how frontend implementations represent this in a config
- * @param save_ Suggests that this should or should not be saved to a frontend config file
- * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
- * @param other_setting_ A second Setting to associate to this one in metadata
- */
+ /// @brief Sets a default value, minimum value, maximum value, and label.
+ /// @param linkage Setting registry
+ /// @param default_val Initial value of the setting, and default value of the setting
+ /// @param min_val Sets the minimum allowed value of the setting
+ /// @param max_val Sets the maximum allowed value of the setting
+ /// @param name Label for the setting
+ /// @param category_ Category of the setting AKA INI group
+ /// @param specialization_ Suggestion for how frontend implementations represent this in a config
+ /// @param save_ Suggests that this should or should not be saved to a frontend config file
+ /// @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
+ /// @param other_setting_ A second Setting to associate to this one in metadata
template
- explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val,
- const Type& max_val, const std::string& name, Category category_,
- u32 specialization_ = Specialization::Default, bool save_ = true,
- bool runtime_modifiable_ = false,
- typename std::enable_if::type other_setting_ = nullptr)
- : Setting{linkage, default_val, min_val,
- max_val, name, category_,
- specialization_, save_, runtime_modifiable_,
- other_setting_} {
+ explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, const Type& max_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(ranged)
+ : Setting{linkage, default_val, min_val, max_val, name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
+ linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
+ }
+
+ template
+ explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(ranged)
+ : Setting{linkage, default_val, EnumMetadata::GetFirst(), EnumMetadata::GetLast(), name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
}
diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h
index 6cc8cdf741..ad84f39dc7 100644
--- a/src/core/hle/service/am/applet.h
+++ b/src/core/hle/service/am/applet.h
@@ -8,6 +8,7 @@
#include
#include
+#include
#include "common/math_util.h"
#include "core/hle/service/apm/apm_controller.h"
@@ -23,6 +24,7 @@
#include "core/hle/service/am/hid_registration.h"
#include "core/hle/service/am/lifecycle_manager.h"
#include "core/hle/service/am/process_holder.h"
+#include "core/hle/service/am/service/storage.h"
namespace Service::AM {
@@ -97,6 +99,9 @@ struct Applet {
std::deque> preselected_user_launch_parameter{};
std::deque> friend_invitation_storage_channel{};
+ // Context Stack
+ std::stack> context_stack{};
+
// Caller applet
std::weak_ptr