Compare commits
9 commits
36fae75437
...
4e9ede3e85
Author | SHA1 | Date | |
---|---|---|---|
4e9ede3e85 | |||
cab8248be2 | |||
5a1353b316 | |||
417e09cc18 | |||
c11e352141 | |||
815d85677a | |||
f422d855b7 | |||
03c7d6ce4a | |||
824dc6948e |
31 changed files with 1094 additions and 228 deletions
169
CMakeLists.txt
169
CMakeLists.txt
|
@ -139,65 +139,50 @@ endif()
|
||||||
|
|
||||||
# Set bundled sdl2/qt as dependent options.
|
# 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
|
# 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)
|
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()
|
|
||||||
|
|
||||||
if (ENABLE_SDL2)
|
if (ENABLE_SDL2)
|
||||||
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
|
# 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}")
|
option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}")
|
||||||
endif()
|
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_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)
|
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF)
|
||||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
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_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
option(ENABLE_WIFI_SCAN "Enable WiFi scanning" OFF)
|
option(ENABLE_WIFI_SCAN "Enable WiFi scanning" OFF)
|
||||||
|
|
||||||
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
|
cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF "ENABLE_QT" OFF)
|
||||||
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)
|
|
||||||
|
|
||||||
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
||||||
|
|
||||||
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ${EXT_DEFAULT})
|
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF)
|
||||||
|
if (YUZU_USE_PRECOMPILED_HEADERS)
|
||||||
# TODO(crueter): CI this?
|
message(STATUS "Using Precompiled Headers.")
|
||||||
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
|
set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
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_ENABLE_LTO "Enable link-time optimization" OFF)
|
option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
|
||||||
if(YUZU_ENABLE_LTO)
|
if(YUZU_ENABLE_LTO)
|
||||||
include(CheckIPOSupported)
|
include(CheckIPOSupported)
|
||||||
|
@ -205,17 +190,42 @@ if(YUZU_ENABLE_LTO)
|
||||||
if(NOT COMPILER_SUPPORTS_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.")
|
message(FATAL_ERROR "Your compiler does not support interprocedural optimization (IPO). Re-run CMake with -DYUZU_ENABLE_LTO=OFF.")
|
||||||
endif()
|
endif()
|
||||||
|
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO})
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO})
|
||||||
endif()
|
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)
|
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")
|
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)
|
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
|
||||||
|
|
||||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
set(DEFAULT_ENABLE_OPENSSL ON)
|
||||||
|
@ -228,15 +238,12 @@ if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN)
|
||||||
# your own copy of it.
|
# your own copy of it.
|
||||||
set(DEFAULT_ENABLE_OPENSSL OFF)
|
set(DEFAULT_ENABLE_OPENSSL OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_WEB_SERVICE)
|
if (ENABLE_WEB_SERVICE)
|
||||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
set(DEFAULT_ENABLE_OPENSSL ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||||
|
|
||||||
if (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()
|
endif()
|
||||||
|
|
||||||
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
|
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
|
||||||
|
@ -263,21 +270,6 @@ if (ANDROID)
|
||||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # Workaround for Oboe
|
set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # Workaround for Oboe
|
||||||
endif()
|
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
|
# Default to a Release build
|
||||||
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
|
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
|
||||||
|
@ -894,24 +886,47 @@ if (MSVC AND CXX_CLANG)
|
||||||
link_libraries(llvm-mingw-runtime)
|
link_libraries(llvm-mingw-runtime)
|
||||||
endif()
|
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)
|
if (YUZU_USE_FASTER_LD)
|
||||||
# fallback if everything fails (bfd)
|
find_program(LINKER_BFD bfd)
|
||||||
set(LINKER bfd)
|
if (LINKER_BFD)
|
||||||
# clang should always use lld
|
set(LINKER bfd)
|
||||||
find_program(LLD lld)
|
endif()
|
||||||
if (LLD)
|
|
||||||
|
find_program(LINKER_LLD lld)
|
||||||
|
if (LINKER_LLD)
|
||||||
set(LINKER lld)
|
set(LINKER lld)
|
||||||
endif()
|
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)
|
if (CXX_GCC)
|
||||||
find_program(MOLD mold)
|
find_program(LINKER_MOLD mold)
|
||||||
if (MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
|
if (LINKER_MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
|
||||||
set(LINKER mold)
|
set(LINKER mold)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_program(LINKER_GOLD gold)
|
||||||
|
if (LINKER_GOLD)
|
||||||
|
set(LINKER gold)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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()
|
||||||
message(NOTICE "Selecting ${LINKER} as linker")
|
|
||||||
add_link_options("-fuse-ld=${LINKER}")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set runtime library to MD/MDd for all configurations
|
# Set runtime library to MD/MDd for all configurations
|
||||||
|
|
|
@ -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_base_url "https://github.com/eden-emulator/")
|
||||||
set(package_repo "no_platform")
|
set(package_repo "no_platform")
|
||||||
set(package_extension "no_platform")
|
set(package_extension "no_platform")
|
||||||
|
set(CACHE_KEY "")
|
||||||
|
|
||||||
# TODO(crueter): Need to convert ffmpeg to a CI.
|
# TODO(crueter): Need to convert ffmpeg to a CI.
|
||||||
if (WIN32 OR FORCE_WIN_ARCHIVES)
|
if (WIN32 OR FORCE_WIN_ARCHIVES)
|
||||||
|
@ -33,8 +34,9 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "No package available for this platform")
|
message(FATAL_ERROR "No package available for this platform")
|
||||||
endif()
|
endif()
|
||||||
set(package_url "${package_base_url}${package_repo}")
|
string(CONCAT package_url "${package_base_url}" "${package_repo}")
|
||||||
set(full_url ${package_url}${remote_path}${lib_name}${package_extension})
|
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!
|
# TODO(crueter): DELETE THIS ENTIRELY, GLORY BE TO THE CI!
|
||||||
AddPackage(
|
AddPackage(
|
||||||
|
@ -47,26 +49,12 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi
|
||||||
# TODO(crueter): hash
|
# TODO(crueter): hash
|
||||||
)
|
)
|
||||||
|
|
||||||
set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE)
|
if (DEFINED ${cpm_key}_SOURCE_DIR)
|
||||||
message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}")
|
set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE)
|
||||||
endfunction()
|
message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}")
|
||||||
|
else()
|
||||||
function(download_moltenvk_external platform version)
|
message(FATAL_ERROR "AddPackage did not set ${cpm_key}_SOURCE_DIR")
|
||||||
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")
|
|
||||||
endif()
|
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()
|
endfunction()
|
||||||
|
|
||||||
# Determine installation parameters for OS, architecture, and compiler
|
# 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(host "linux")
|
||||||
set(type "desktop")
|
set(type "desktop")
|
||||||
set(arch "linux_gcc_64")
|
set(arch "linux_gcc_64")
|
||||||
set(arch_path "linux")
|
set(arch_path "gcc_64")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(${host_out} "${host}" PARENT_SCOPE)
|
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")
|
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
|
||||||
if (tool)
|
if (tool)
|
||||||
set(prefix "${base_path}/Tools")
|
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()
|
else()
|
||||||
set(prefix "${base_path}/${target}/${arch_path}")
|
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)
|
if (YUZU_USE_QT_MULTIMEDIA)
|
||||||
set(install_args ${install_args} qtmultimedia)
|
list(APPEND install_args qtmultimedia)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (YUZU_USE_QT_WEB_ENGINE)
|
if (YUZU_USE_QT_WEB_ENGINE)
|
||||||
set(install_args ${install_args} qtpositioning qtwebchannel qtwebengine)
|
list(APPEND install_args qtpositioning qtwebchannel qtwebengine)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ${YUZU_QT_MIRROR} STREQUAL "")
|
if (NOT "${YUZU_QT_MIRROR}" STREQUAL "")
|
||||||
message(STATUS "Using Qt mirror ${YUZU_QT_MIRROR}")
|
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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Install Args ${install_args}")
|
message(STATUS "Install Args: ${install_args}")
|
||||||
|
|
||||||
if (NOT EXISTS "${prefix}")
|
if (NOT EXISTS "${prefix}")
|
||||||
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
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")
|
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0")
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(aqt_path "${base_path}/aqt.exe")
|
set(aqt_path "${base_path}/aqt.exe")
|
||||||
if (NOT EXISTS "${aqt_path}")
|
if (NOT EXISTS "${aqt_path}")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt.exe" "${aqt_path}" SHOW_PROGRESS)
|
||||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
endif()
|
||||||
${aqt_path} SHOW_PROGRESS)
|
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()
|
endif()
|
||||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
|
||||||
WORKING_DIRECTORY ${base_path})
|
|
||||||
elseif (APPLE)
|
elseif (APPLE)
|
||||||
set(aqt_path "${base_path}/aqt-macos")
|
set(aqt_path "${base_path}/aqt-macos")
|
||||||
if (NOT EXISTS "${aqt_path}")
|
if (NOT EXISTS "${aqt_path}")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt-macos" "${aqt_path}" SHOW_PROGRESS)
|
||||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
endif()
|
||||||
${aqt_path} SHOW_PROGRESS)
|
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()
|
endif()
|
||||||
execute_process(COMMAND chmod +x ${aqt_path})
|
|
||||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
|
||||||
WORKING_DIRECTORY ${base_path})
|
|
||||||
else()
|
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")
|
set(aqt_install_path "${base_path}/aqt")
|
||||||
file(MAKE_DIRECTORY "${aqt_install_path}")
|
file(MAKE_DIRECTORY "${aqt_install_path}")
|
||||||
|
|
||||||
execute_process(COMMAND python3 -m pip install --target=${aqt_install_path} aqtinstall
|
execute_process(COMMAND "${PYTHON3_EXECUTABLE}" -m pip install --target="${aqt_install_path}" aqtinstall
|
||||||
WORKING_DIRECTORY ${base_path})
|
WORKING_DIRECTORY "${base_path}"
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
|
RESULT_VARIABLE pip_res
|
||||||
WORKING_DIRECTORY ${base_path})
|
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()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
|
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
|
||||||
|
@ -210,7 +221,7 @@ endfunction()
|
||||||
function(download_qt target)
|
function(download_qt target)
|
||||||
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
|
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}")
|
file(MAKE_DIRECTORY "${base_path}")
|
||||||
|
|
||||||
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${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)
|
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(download_moltenvk)
|
function(download_moltenvk version platform)
|
||||||
set(MOLTENVK_PLATFORM "macOS")
|
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_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
|
||||||
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
|
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()
|
|
||||||
|
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
if(NOT EXISTS "${MOLTENVK_DIR}")
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
if(NOT EXISTS "${MOLTENVK_TAR}")
|
||||||
endif()
|
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.
|
execute_process(
|
||||||
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${MOLTENVK_PLATFORM}")
|
COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
|
||||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
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()
|
endfunction()
|
||||||
|
|
||||||
function(get_external_prefix lib_name prefix_var)
|
|
||||||
set(${prefix_var} "${CMAKE_BINARY_DIR}/externals/${lib_name}" PARENT_SCOPE)
|
|
||||||
endfunction()
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
||||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||||
|
|
||||||
```sh
|
```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.
|
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
|
||||||
|
|
|
@ -31,7 +31,7 @@ Notes:
|
||||||
* Currently, build fails without this
|
* Currently, build fails without this
|
||||||
- `YUZU_USE_FASTER_LD` (ON) Check if a faster linker is available
|
- `YUZU_USE_FASTER_LD` (ON) Check if a faster linker is available
|
||||||
* Only available on UNIX
|
* 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)
|
- `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
|
- `ENABLE_OPENSSL` (ON for Linux and *BSD) Enable OpenSSL backend for the ssl service
|
||||||
* Always enabled if the web service is enabled
|
* Always enabled if the web service is enabled
|
||||||
|
|
|
@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
var isActivityRecreated = false
|
var isActivityRecreated = false
|
||||||
private lateinit var nfcReader: NfcReader
|
private lateinit var nfcReader: NfcReader
|
||||||
|
|
||||||
|
private var touchDownTime: Long = 0
|
||||||
|
private val maxTapDuration = 500L
|
||||||
|
|
||||||
private val gyro = FloatArray(3)
|
private val gyro = FloatArray(3)
|
||||||
private val accel = FloatArray(3)
|
private val accel = FloatArray(3)
|
||||||
private var motionTimestamp: Long = 0
|
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() {
|
fun onEmulationStarted() {
|
||||||
emulationViewModel.setEmulationStarted(true)
|
emulationViewModel.setEmulationStarted(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||||
FRAME_INTERPOLATION("frame_interpolation"),
|
FRAME_INTERPOLATION("frame_interpolation"),
|
||||||
// FRAME_SKIPPING("frame_skipping"),
|
// FRAME_SKIPPING("frame_skipping"),
|
||||||
|
|
||||||
|
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
|
||||||
|
|
||||||
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
||||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||||
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
|
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
|
||||||
LOGIN_SHARE_APPLET("login_share_applet_mode"),
|
LOGIN_SHARE_APPLET("login_share_applet_mode"),
|
||||||
WIFI_WEB_AUTH_APPLET("wifi_web_auth_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)
|
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||||
|
|
|
@ -12,6 +12,7 @@ object Settings {
|
||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
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_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||||
SECTION_AUDIO(R.string.preferences_audio),
|
SECTION_AUDIO(R.string.preferences_audio),
|
||||||
SECTION_INPUT(R.string.preferences_controls),
|
SECTION_INPUT(R.string.preferences_controls),
|
||||||
|
|
|
@ -96,6 +96,7 @@ abstract class SettingsItem(
|
||||||
const val TYPE_INT_SINGLE_CHOICE = 9
|
const val TYPE_INT_SINGLE_CHOICE = 9
|
||||||
const val TYPE_INPUT_PROFILE = 10
|
const val TYPE_INPUT_PROFILE = 10
|
||||||
const val TYPE_STRING_INPUT = 11
|
const val TYPE_STRING_INPUT = 11
|
||||||
|
const val TYPE_SPINBOX = 12
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
|
|
||||||
|
@ -385,6 +386,22 @@ abstract class SettingsItem(
|
||||||
warningMessage = R.string.warning_resolution
|
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(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
|
@ -61,6 +61,10 @@ class SettingsAdapter(
|
||||||
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_SPINBOX -> {
|
||||||
|
SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_SUBMENU -> {
|
SettingsItem.TYPE_SUBMENU -> {
|
||||||
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
@ -191,6 +195,14 @@ class SettingsAdapter(
|
||||||
position
|
position
|
||||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
).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) {
|
fun onSubmenuClick(item: SubmenuSetting) {
|
||||||
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.text.TextWatcher
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
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.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
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.NativeInput
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
|
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.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
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.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.StringInputSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||||
|
@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||||
|
|
||||||
private lateinit var sliderBinding: DialogSliderBinding
|
private lateinit var sliderBinding: DialogSliderBinding
|
||||||
private lateinit var stringInputBinding: DialogEditTextBinding
|
private lateinit var stringInputBinding: DialogEditTextBinding
|
||||||
|
private lateinit var spinboxBinding: DialogSpinboxBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||||
.create()
|
.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 -> {
|
SettingsItem.TYPE_STRING_INPUT -> {
|
||||||
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
||||||
val item = settingsViewModel.clickedItem as StringInputSetting
|
val item = settingsViewModel.clickedItem as StringInputSetting
|
||||||
|
@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||||
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
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 -> {
|
is StringInputSetting -> {
|
||||||
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
||||||
stringInputSetting.setSelectedValue(
|
stringInputSetting.setSelectedValue(
|
||||||
|
|
|
@ -97,6 +97,7 @@ class SettingsFragmentPresenter(
|
||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||||
|
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||||
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||||
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
||||||
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
||||||
|
@ -156,6 +157,14 @@ class SettingsFragmentPresenter(
|
||||||
menuKey = MenuTag.SECTION_SOC_OVERLAY
|
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(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
|
@ -264,6 +273,13 @@ class SettingsFragmentPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
sl.apply {
|
||||||
|
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||||
|
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
|
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(HeaderSetting(R.string.stats_overlay_customization))
|
add(HeaderSetting(R.string.stats_overlay_customization))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
private var perfStatsUpdater: (() -> Unit)? = null
|
private var perfStatsUpdater: (() -> Unit)? = null
|
||||||
private var socUpdater: (() -> Unit)? = null
|
private var socUpdater: (() -> Unit)? = null
|
||||||
|
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var isOverlayVisible = true
|
||||||
|
|
||||||
private var _binding: FragmentEmulationBinding? = null
|
private var _binding: FragmentEmulationBinding? = null
|
||||||
|
|
||||||
private val binding get() = _binding!!
|
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
|
* 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 ->
|
return suspendCoroutine { continuation ->
|
||||||
requireActivity().runOnUiThread {
|
requireActivity().runOnUiThread {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
updateShowStatsOverlay()
|
updateShowStatsOverlay()
|
||||||
updateSocOverlay()
|
updateSocOverlay()
|
||||||
|
|
||||||
|
initializeOverlayAutoHide()
|
||||||
|
|
||||||
// Re update binding when the specs values get initialized properly
|
// Re update binding when the specs values get initialized properly
|
||||||
binding.inGameMenu.getHeaderView(0).apply {
|
binding.inGameMenu.getHeaderView(0).apply {
|
||||||
val titleView = findViewById<TextView>(R.id.text_game_title)
|
val titleView = findViewById<TextView>(R.id.text_game_title)
|
||||||
|
@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
updatePauseMenuEntry(emulationState.isPaused)
|
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() {
|
private fun resetInputOverlay() {
|
||||||
|
@ -924,6 +937,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
IntSetting.OVERLAY_OPACITY.reset()
|
IntSetting.OVERLAY_OPACITY.reset()
|
||||||
binding.surfaceInputOverlay.post {
|
binding.surfaceInputOverlay.post {
|
||||||
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
|
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
|
||||||
|
binding.surfaceInputOverlay.resetIndividualControlScale()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||||
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||||
status == BatteryManager.BATTERY_STATUS_FULL
|
status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
|
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
sb.append(" ${getString(R.string.charging)}")
|
sb.append(" ${getString(R.string.charging)}")
|
||||||
|
@ -1546,6 +1560,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
|
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
|
||||||
setControlScale(50)
|
setControlScale(50)
|
||||||
setControlOpacity(100)
|
setControlOpacity(100)
|
||||||
|
binding.surfaceInputOverlay.resetIndividualControlScale()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -1726,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
||||||
private val socUpdateHandler = Handler(Looper.myLooper()!!)
|
private val socUpdateHandler = Handler(Looper.myLooper()!!)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.VectorDrawable
|
import android.graphics.drawable.VectorDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = 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
|
private lateinit var windowInsets: WindowInsets
|
||||||
|
|
||||||
var layout = OverlayLayout.Landscape
|
var layout = OverlayLayout.Landscape
|
||||||
|
@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
buttonBeingConfigured = button
|
buttonBeingConfigured = button
|
||||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
|
||||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(event.getX(pointerIndex) - touchStartX).let { it * it } +
|
||||||
return true
|
(event.getY(pointerIndex) - touchStartY).let { it * it }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moveDistance > moveThreshold) {
|
||||||
|
hasMoved = true
|
||||||
|
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
|
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
|
||||||
// Persist button position by saving new place.
|
if (!hasMoved) {
|
||||||
saveControlPosition(
|
showScaleDialog(
|
||||||
buttonBeingConfigured!!.overlayControlData.id,
|
buttonBeingConfigured,
|
||||||
buttonBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
buttonBeingConfigured!!.bounds.centerY(),
|
null,
|
||||||
layout
|
fingerPositionX,
|
||||||
)
|
fingerPositionY
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
saveControlPosition(
|
||||||
|
buttonBeingConfigured!!.overlayControlData.id,
|
||||||
|
buttonBeingConfigured!!.bounds.centerX(),
|
||||||
|
buttonBeingConfigured!!.bounds.centerY(),
|
||||||
|
individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale,
|
||||||
|
layout
|
||||||
|
)
|
||||||
|
}
|
||||||
buttonBeingConfigured = null
|
buttonBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
dpadBeingConfigured = dpad
|
dpadBeingConfigured = dpad
|
||||||
dpadBeingConfigured!!.onConfigureTouch(event)
|
dpadBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
|
||||||
dpadBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(event.getX(pointerIndex) - touchStartX).let { it * it } +
|
||||||
return true
|
(event.getY(pointerIndex) - touchStartY).let { it * it }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moveDistance > moveThreshold) {
|
||||||
|
hasMoved = true
|
||||||
|
dpadBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
|
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
|
||||||
// Persist button position by saving new place.
|
if (!hasMoved) {
|
||||||
saveControlPosition(
|
// This was a click, show scale dialog for dpad
|
||||||
OverlayControl.COMBINED_DPAD.id,
|
showScaleDialog(
|
||||||
dpadBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
dpadBeingConfigured!!.bounds.centerY(),
|
dpadBeingConfigured,
|
||||||
layout
|
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
|
dpadBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
joystickBeingConfigured = joystick
|
joystickBeingConfigured = joystick
|
||||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
joystickBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
|
||||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(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_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
|
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
|
||||||
saveControlPosition(
|
if (!hasMoved) {
|
||||||
joystickBeingConfigured!!.prefId,
|
showScaleDialog(
|
||||||
joystickBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
joystickBeingConfigured!!.bounds.centerY(),
|
null,
|
||||||
layout
|
joystickBeingConfigured,
|
||||||
)
|
fingerPositionX,
|
||||||
|
fingerPositionY
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
saveControlPosition(
|
||||||
|
joystickBeingConfigured!!.prefId,
|
||||||
|
joystickBeingConfigured!!.bounds.centerX(),
|
||||||
|
joystickBeingConfigured!!.bounds.centerY(),
|
||||||
|
individuaScale = joystickBeingConfigured!!.individualScale,
|
||||||
|
layout
|
||||||
|
)
|
||||||
|
}
|
||||||
joystickBeingConfigured = null
|
joystickBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
invalidate()
|
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 windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
|
||||||
val min = windowSize.first
|
val min = windowSize.first
|
||||||
val max = windowSize.second
|
val max = windowSize.second
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
val data = overlayControlData.firstOrNull { it.id == id }
|
val data = overlayControlData.firstOrNull { it.id == id }
|
||||||
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
|
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
|
||||||
|
|
||||||
when (layout) {
|
when (layout) {
|
||||||
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
|
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
|
||||||
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
|
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
|
||||||
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
|
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data?.individualScale = individuaScale
|
||||||
|
|
||||||
NativeConfig.setOverlayControlData(overlayControlData)
|
NativeConfig.setOverlayControlData(overlayControlData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsInEditMode(editMode: Boolean) {
|
fun setIsInEditMode(editMode: Boolean) {
|
||||||
inEditMode = editMode
|
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
|
* Applies and saves all default values for the overlay
|
||||||
*/
|
*/
|
||||||
|
@ -664,12 +830,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
overlayControlData.forEach {
|
overlayControlData.forEach {
|
||||||
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
|
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
|
||||||
|
it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!!
|
||||||
}
|
}
|
||||||
NativeConfig.setOverlayControlData(overlayControlData)
|
NativeConfig.setOverlayControlData(overlayControlData)
|
||||||
|
|
||||||
refreshControls()
|
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) {
|
private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
for (data in overlayControlData) {
|
for (data in overlayControlData) {
|
||||||
|
@ -860,6 +1038,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
scale *= overlayControlData.individualScale
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableButton.
|
// Initialize the InputOverlayDrawableButton.
|
||||||
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
||||||
val pressedStateBitmap = getBitmap(context, pressedResId, 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.
|
// Resources handle for fetching the initial Drawable resource.
|
||||||
val res = context.resources
|
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
|
// Decide scale based on button ID and user preference
|
||||||
var scale = 0.25f
|
var scale = 0.25f
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
if (dpadData != null) {
|
||||||
|
scale *= dpadData.individualScale
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableDpad.
|
// Initialize the InputOverlayDrawableDpad.
|
||||||
val defaultStateBitmap =
|
val defaultStateBitmap =
|
||||||
getBitmap(context, defaultResId, scale)
|
getBitmap(context, defaultResId, scale)
|
||||||
|
@ -1000,6 +1190,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
scale *= overlayControlData.individualScale
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableJoystick.
|
// Initialize the InputOverlayDrawableJoystick.
|
||||||
val bitmapOuter = getBitmap(context, resOuter, scale)
|
val bitmapOuter = getBitmap(context, resOuter, scale)
|
||||||
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ class InputOverlayDrawableDpad(
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|
||||||
|
var individualScale: Float = 1.0f
|
||||||
|
|
||||||
private val defaultStateBitmap: BitmapDrawable
|
private val defaultStateBitmap: BitmapDrawable
|
||||||
private val pressedOneDirectionStateBitmap: BitmapDrawable
|
private val pressedOneDirectionStateBitmap: BitmapDrawable
|
||||||
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
|
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick(
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|
||||||
|
var individualScale: Float = 1.0f
|
||||||
|
|
||||||
private var opacity: Int = 0
|
private var opacity: Int = 0
|
||||||
|
|
||||||
private var virtBounds: Rect
|
private var virtBounds: Rect
|
||||||
|
|
|
@ -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<MaterialButton>(R.id.resetButton)
|
||||||
|
val confirmButton = view.findViewById<MaterialButton>(R.id.confirmButton)
|
||||||
|
val cancelButton = view.findViewById<MaterialButton>(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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay.model
|
package org.yuzu.yuzu_emu.overlay.model
|
||||||
|
|
||||||
|
@ -12,126 +12,144 @@ enum class OverlayControl(
|
||||||
val defaultVisibility: Boolean,
|
val defaultVisibility: Boolean,
|
||||||
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
|
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
|
||||||
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
|
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
|
||||||
@IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
|
@IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>,
|
||||||
|
val defaultIndividualScaleResource: Float,
|
||||||
) {
|
) {
|
||||||
BUTTON_A(
|
BUTTON_A(
|
||||||
"button_a",
|
"button_a",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
|
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_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(
|
||||||
"button_b",
|
"button_b",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
|
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_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(
|
||||||
"button_x",
|
"button_x",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
|
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_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(
|
||||||
"button_y",
|
"button_y",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
|
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_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(
|
||||||
"button_plus",
|
"button_plus",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
|
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_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(
|
||||||
"button_minus",
|
"button_minus",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
|
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_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(
|
||||||
"button_home",
|
"button_home",
|
||||||
false,
|
false,
|
||||||
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
|
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_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(
|
||||||
"button_capture",
|
"button_capture",
|
||||||
false,
|
false,
|
||||||
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
|
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_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(
|
||||||
"button_l",
|
"button_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
|
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_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(
|
||||||
"button_r",
|
"button_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
|
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_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(
|
||||||
"button_zl",
|
"button_zl",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
|
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_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(
|
||||||
"button_zr",
|
"button_zr",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
|
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_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(
|
||||||
"button_stick_l",
|
"button_stick_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
|
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_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(
|
||||||
"button_stick_r",
|
"button_stick_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
|
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_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(
|
||||||
"stick_l",
|
"stick_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
|
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_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(
|
||||||
"stick_r",
|
"stick_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
|
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_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(
|
||||||
"combined_dpad",
|
"combined_dpad",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
|
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_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<Double, Double> {
|
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
|
||||||
|
@ -173,7 +191,8 @@ enum class OverlayControl(
|
||||||
defaultVisibility,
|
defaultVisibility,
|
||||||
getDefaultPositionForLayout(OverlayLayout.Landscape),
|
getDefaultPositionForLayout(OverlayLayout.Landscape),
|
||||||
getDefaultPositionForLayout(OverlayLayout.Portrait),
|
getDefaultPositionForLayout(OverlayLayout.Portrait),
|
||||||
getDefaultPositionForLayout(OverlayLayout.Foldable)
|
getDefaultPositionForLayout(OverlayLayout.Foldable),
|
||||||
|
defaultIndividualScaleResource
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay.model
|
package org.yuzu.yuzu_emu.overlay.model
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ data class OverlayControlData(
|
||||||
var enabled: Boolean,
|
var enabled: Boolean,
|
||||||
var landscapePosition: Pair<Double, Double>,
|
var landscapePosition: Pair<Double, Double>,
|
||||||
var portraitPosition: Pair<Double, Double>,
|
var portraitPosition: Pair<Double, Double>,
|
||||||
var foldablePosition: Pair<Double, Double>
|
var foldablePosition: Pair<Double, Double>,
|
||||||
|
var individualScale: Float
|
||||||
) {
|
) {
|
||||||
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
|
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
|
||||||
when (layout) {
|
when (layout) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
@ -170,7 +170,8 @@ object DirectoryInitialization {
|
||||||
buttonEnabled,
|
buttonEnabled,
|
||||||
Pair(landscapeXPosition, landscapeYPosition),
|
Pair(landscapeXPosition, landscapeYPosition),
|
||||||
Pair(portraitXPosition, portraitYPosition),
|
Pair(portraitXPosition, portraitYPosition),
|
||||||
Pair(foldableXPosition, foldableYPosition)
|
Pair(foldableXPosition, foldableYPosition),
|
||||||
|
OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f
|
||||||
)
|
)
|
||||||
overlayControlDataMap[buttonId] = controlData
|
overlayControlDataMap[buttonId] = controlData
|
||||||
setOverlayData = true
|
setOverlayData = true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <common/logging/log.h>
|
#include <common/logging/log.h>
|
||||||
#include <input_common/main.h>
|
#include <input_common/main.h>
|
||||||
|
@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() {
|
||||||
ReadDoubleSetting(std::string("foldable\\x_position"));
|
ReadDoubleSetting(std::string("foldable\\x_position"));
|
||||||
control_data.foldable_position.second =
|
control_data.foldable_position.second =
|
||||||
ReadDoubleSetting(std::string("foldable\\y_position"));
|
ReadDoubleSetting(std::string("foldable\\y_position"));
|
||||||
|
control_data.individual_scale = static_cast<float>(ReadDoubleSetting(std::string("individual_scale")));
|
||||||
AndroidSettings::values.overlay_control_data.push_back(control_data);
|
AndroidSettings::values.overlay_control_data.push_back(control_data);
|
||||||
}
|
}
|
||||||
EndArray();
|
EndArray();
|
||||||
|
@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() {
|
||||||
control_data.foldable_position.first);
|
control_data.foldable_position.first);
|
||||||
WriteDoubleSetting(std::string("foldable\\y_position"),
|
WriteDoubleSetting(std::string("foldable\\y_position"),
|
||||||
control_data.foldable_position.second);
|
control_data.foldable_position.second);
|
||||||
|
WriteDoubleSetting(std::string("individual_scale"), static_cast<double>(control_data.individual_scale));
|
||||||
}
|
}
|
||||||
EndArray();
|
EndArray();
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace AndroidSettings {
|
||||||
std::pair<double, double> landscape_position;
|
std::pair<double, double> landscape_position;
|
||||||
std::pair<double, double> portrait_position;
|
std::pair<double, double> portrait_position;
|
||||||
std::pair<double, double> foldable_position;
|
std::pair<double, double> foldable_position;
|
||||||
|
float individual_scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Values {
|
struct Values {
|
||||||
|
@ -79,6 +80,15 @@ namespace AndroidSettings {
|
||||||
Settings::Category::Overlay,
|
Settings::Category::Overlay,
|
||||||
Settings::Specialization::Paired, true,
|
Settings::Specialization::Paired, true,
|
||||||
true};
|
true};
|
||||||
|
Settings::Setting<bool> enable_input_overlay_auto_hide{linkage, false,
|
||||||
|
"enable_input_overlay_auto_hide",
|
||||||
|
Settings::Category::Overlay,
|
||||||
|
Settings::Specialization::Default, true,
|
||||||
|
true,};
|
||||||
|
|
||||||
|
Settings::Setting<u32> 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<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
||||||
Settings::Category::Overlay,
|
Settings::Category::Overlay,
|
||||||
Settings::Specialization::Default, true,
|
Settings::Specialization::Default, true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN
|
||||||
env->NewObject(Common::Android::GetOverlayControlDataClass(),
|
env->NewObject(Common::Android::GetOverlayControlDataClass(),
|
||||||
Common::Android::GetOverlayControlDataConstructor(),
|
Common::Android::GetOverlayControlDataConstructor(),
|
||||||
Common::Android::ToJString(env, control_data.id), control_data.enabled,
|
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);
|
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
|
||||||
}
|
}
|
||||||
return joverlayControlDataArray;
|
return joverlayControlDataArray;
|
||||||
|
@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
|
||||||
env,
|
env,
|
||||||
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
|
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
|
||||||
|
|
||||||
|
float individual_scale = static_cast<float>(env->GetFloatField(
|
||||||
|
joverlayControlData, Common::Android::GetOverlayControlDataIndividualScaleField()));
|
||||||
|
|
||||||
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
|
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
|
||||||
Common::Android::GetJString(env, jidString), enabled, landscape_position,
|
Common::Android::GetJString(env, jidString), enabled, landscape_position,
|
||||||
portrait_position, foldable_position});
|
portrait_position, foldable_position, individual_scale});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
src/android/app/src/main/res/layout/dialog_overlay_scale.xml
Normal file
74
src/android/app/src/main/res/layout/dialog_overlay_scale.xml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="320dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:cardBackgroundColor="#CC222222">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/scaleValueText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1.0x"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/scaleSlider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:valueFrom="0.5"
|
||||||
|
android:valueTo="4.0"
|
||||||
|
android:stepSize="0.1"
|
||||||
|
android:value="1.0"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:trackColorActive="@color/eden_border_gradient_start"
|
||||||
|
app:trackColorInactive="@color/eden_border_gradient_end"
|
||||||
|
app:tickColor="@color/eden_border_gradient_start"/>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/resetButton"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/reset"
|
||||||
|
android:layout_marginEnd="4dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/cancelButton"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
android:layout_marginEnd="4dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/confirmButton"
|
||||||
|
style="@style/Widget.Material3.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/eden_button_secondary_bg"
|
||||||
|
android:textColor="@color/eden_border_gradient_end"
|
||||||
|
android:text="@string/confirm" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
55
src/android/app/src/main/res/layout/dialog_spinbox.xml
Normal file
55
src/android/app/src/main/res/layout/dialog_spinbox.xml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="@dimen/spacing_large"
|
||||||
|
android:paddingRight="@dimen/spacing_large">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_decrement"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/decrement"
|
||||||
|
android:text="-" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/text_input_layout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/spacing_medlarge"
|
||||||
|
android:layout_marginRight="@dimen/spacing_medlarge"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Value">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/edit_value"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:textAlignment="textStart"
|
||||||
|
tools:text="0" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_increment"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/increment"
|
||||||
|
android:text="+" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -12,6 +12,24 @@
|
||||||
<string name="app_notification_channel_id" translatable="false">Eden</string>
|
<string name="app_notification_channel_id" translatable="false">Eden</string>
|
||||||
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
|
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
|
||||||
<string name="app_notification_running">Eden is Running</string>
|
<string name="app_notification_running">Eden is Running</string>
|
||||||
|
<string name="seconds">Seconds</string>
|
||||||
|
|
||||||
|
<!-- Spinbox strings -->
|
||||||
|
<string name="increment">Increment</string>
|
||||||
|
<string name="decrement">Decrement</string>
|
||||||
|
<string name="value">Value</string>
|
||||||
|
<string name="value_too_low">Value must be at least %1$d</string>
|
||||||
|
<string name="value_too_high">Value must be at most %1$d</string>
|
||||||
|
<string name="invalid_value">Invalid value</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Input Overlay -->
|
||||||
|
<string name="overlay_auto_hide">Overlay Auto Hide</string>
|
||||||
|
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
|
||||||
|
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
|
||||||
|
|
||||||
|
<string name="input_overlay_options">Input Overlay</string>
|
||||||
|
<string name="input_overlay_options_description">Configure on-screen controls</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Stats Overlay settings -->
|
<!-- Stats Overlay settings -->
|
||||||
|
@ -855,6 +873,7 @@
|
||||||
<string name="touchscreen">Touchscreen</string>
|
<string name="touchscreen">Touchscreen</string>
|
||||||
<string name="lock_drawer">Lock drawer</string>
|
<string name="lock_drawer">Lock drawer</string>
|
||||||
<string name="unlock_drawer">Unlock drawer</string>
|
<string name="unlock_drawer">Unlock drawer</string>
|
||||||
|
<string name="reset">Reset</string>
|
||||||
|
|
||||||
<string name="load_settings">Loading settings…</string>
|
<string name="load_settings">Loading settings…</string>
|
||||||
|
|
||||||
|
@ -870,7 +889,7 @@
|
||||||
<string name="save_load_error">Save/Load Error</string>
|
<string name="save_load_error">Save/Load Error</string>
|
||||||
<string name="fatal_error">Fatal Error</string>
|
<string name="fatal_error">Fatal Error</string>
|
||||||
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes.</string>
|
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes.</string>
|
||||||
<string name="performance_warning">Turning off this setting will significantly degrade performance. It's recommended that you leave this setting enabled.</string>
|
<string name="performance_warning">Turning off this setting will significantly degrade performance. It"s recommended that you leave this setting enabled.</string>
|
||||||
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
|
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
|
||||||
<string name="memory_formatted">%1$s %2$s</string>
|
<string name="memory_formatted">%1$s %2$s</string>
|
||||||
<string name="no_game_present">No bootable game present!</string>
|
<string name="no_game_present">No bootable game present!</string>
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
@ -49,6 +46,7 @@ static jclass s_overlay_control_data_class;
|
||||||
static jmethodID s_overlay_control_data_constructor;
|
static jmethodID s_overlay_control_data_constructor;
|
||||||
static jfieldID s_overlay_control_data_id_field;
|
static jfieldID s_overlay_control_data_id_field;
|
||||||
static jfieldID s_overlay_control_data_enabled_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_landscape_position_field;
|
||||||
static jfieldID s_overlay_control_data_portrait_position_field;
|
static jfieldID s_overlay_control_data_portrait_position_field;
|
||||||
static jfieldID s_overlay_control_data_foldable_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;
|
return s_overlay_control_data_enabled_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jfieldID GetOverlayControlDataIndividualScaleField() {
|
||||||
|
return s_overlay_control_data_individual_scale_field;
|
||||||
|
}
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField() {
|
jfieldID GetOverlayControlDataLandscapePositionField() {
|
||||||
return s_overlay_control_data_landscape_position_field;
|
return s_overlay_control_data_landscape_position_field;
|
||||||
}
|
}
|
||||||
|
@ -494,7 +496,7 @@ namespace Common::Android {
|
||||||
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
||||||
s_overlay_control_data_constructor =
|
s_overlay_control_data_constructor =
|
||||||
env->GetMethodID(overlay_control_data_class, "<init>",
|
env->GetMethodID(overlay_control_data_class, "<init>",
|
||||||
"(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 =
|
s_overlay_control_data_id_field =
|
||||||
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
||||||
s_overlay_control_data_enabled_field =
|
s_overlay_control_data_enabled_field =
|
||||||
|
@ -505,6 +507,8 @@ namespace Common::Android {
|
||||||
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
||||||
s_overlay_control_data_foldable_position_field =
|
s_overlay_control_data_foldable_position_field =
|
||||||
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
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);
|
env->DeleteLocalRef(overlay_control_data_class);
|
||||||
|
|
||||||
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -67,6 +64,7 @@ jclass GetOverlayControlDataClass();
|
||||||
jmethodID GetOverlayControlDataConstructor();
|
jmethodID GetOverlayControlDataConstructor();
|
||||||
jfieldID GetOverlayControlDataIdField();
|
jfieldID GetOverlayControlDataIdField();
|
||||||
jfieldID GetOverlayControlDataEnabledField();
|
jfieldID GetOverlayControlDataEnabledField();
|
||||||
|
jfieldID GetOverlayControlDataIndividualScaleField();
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField();
|
jfieldID GetOverlayControlDataLandscapePositionField();
|
||||||
jfieldID GetOverlayControlDataPortraitPositionField();
|
jfieldID GetOverlayControlDataPortraitPositionField();
|
||||||
jfieldID GetOverlayControlDataFoldablePositionField();
|
jfieldID GetOverlayControlDataFoldablePositionField();
|
||||||
|
|
|
@ -366,10 +366,10 @@ if (APPLE)
|
||||||
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
|
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||||
|
|
||||||
if (NOT USE_SYSTEM_MOLTENVK)
|
if (YUZU_APPLE_USE_BUNDLED_MONTENVK)
|
||||||
set(MOLTENVK_PLATFORM "macOS")
|
set(MOLTENVK_PLATFORM "macOS")
|
||||||
set(MOLTENVK_VERSION "v1.3.0")
|
set(MOLTENVK_VERSION "v1.3.0")
|
||||||
download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
|
download_moltenvk(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
|
||||||
endif()
|
endif()
|
||||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue