mirror of
https://github.com/visualboyadvance-m/visualboyadvance-m
synced 2025-10-06 08:02:50 +02:00
Compare commits
74 Commits
user-input
...
faudio-con
Author | SHA1 | Date | |
---|---|---|---|
|
ca654aabfa | ||
|
2ce20c4f59 | ||
|
e4ef4aa625 | ||
|
cf5cb40cb9 | ||
|
c450d14311 | ||
|
41572be3f2 | ||
|
4f8da1c574 | ||
|
32091669d4 | ||
|
abd72a5b2e | ||
|
7e6349b19f | ||
|
0782be749e | ||
|
a7b545ab1a | ||
|
1a564f900c | ||
|
3d4e03f85f | ||
|
fc17209ac7 | ||
|
38877ef209 | ||
|
8691a15be8 | ||
|
961fd0304c | ||
|
e2cf6ecba6 | ||
|
7a0826a60c | ||
|
d516683a77 | ||
|
834c7de86c | ||
|
4f1a5dd726 | ||
|
5766b9b9c7 | ||
|
5d8426d317 | ||
|
63ec3528f1 | ||
|
f646c3848c | ||
|
09433875bc | ||
|
05c09ff506 | ||
|
7f78fbb3c5 | ||
|
261e26f488 | ||
|
ed820708af | ||
|
5b8b6a0b47 | ||
|
8809ce26b3 | ||
|
a48625855b | ||
|
55c1477d69 | ||
|
2d7a1ea25b | ||
|
244149c00e | ||
|
c0bcf3bfdf | ||
|
13756bcbf9 | ||
|
fc82e06270 | ||
|
df89beb256 | ||
|
82eda48e8f | ||
|
b47787b317 | ||
|
c9668d9a88 | ||
|
90a56c6937 | ||
|
d377f7abff | ||
|
1fac129746 | ||
|
af7d5f7b89 | ||
|
28f7c2010b | ||
|
486330f23d | ||
|
1ae78a04a8 | ||
|
c776da7120 | ||
|
d543784a3d | ||
|
902c6c8e4b | ||
|
d32be9ddbe | ||
|
b776509287 | ||
|
56eb97c846 | ||
|
9f46c575fd | ||
|
e0402a9b0b | ||
|
3e30f54d5f | ||
|
d73085a88c | ||
|
8eb6a6900f | ||
|
3615137c12 | ||
|
bbd5b76f2a | ||
|
bb3604f333 | ||
|
18a0067ca7 | ||
|
cfdbdc4ec2 | ||
|
32ca2ae42f | ||
|
bad96cf91e | ||
|
62294702e4 | ||
|
72c4f33d63 | ||
|
3fe57f540d | ||
|
1e1a369c8d |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: rkitover
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -11,7 +11,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please try the nightly build from: https://nightly.vba-m.com and factory resetting the emulator from the Help menu.
|
||||
Please try the nightly build from: https://nightly.visualboyadvance-m.org and factory resetting the emulator from the Help menu.
|
||||
On Linux build master from source or use the edge snap.
|
||||
And last but not least, search for existing reports via the filters bar on the issues page.
|
||||
- type: markdown
|
||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: VBA-M Forum
|
||||
url: https://board.vba-m.com/
|
||||
url: https://board.visualboyadvance-m.org/
|
||||
about: For general questions and support please join our forum or our
|
||||
- name: VBA-M IRC Channel
|
||||
url: https://web.libera.chat/#vba-m
|
||||
|
2
.github/ISSUE_TEMPLATE/feature.yml
vendored
2
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please try a nightly build from: https://nightly.vba-m.com to see if your idea has already been implemented.
|
||||
Please try a nightly build from: https://nightly.visualboyadvance-m.org to see if your idea has already been implemented.
|
||||
On Linux build master from source or use the edge snap.
|
||||
And last but not least, search for existing requests via the filters bar on the issues page.
|
||||
- type: textarea
|
||||
|
33
.github/workflows/devkitpro-build.yml
vendored
Normal file
33
.github/workflows/devkitpro-build.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Libretro Devkitpro
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
target_name: [ngc, wii, wiiu, switch]
|
||||
build_type: [release, debug]
|
||||
include:
|
||||
- libretro_build: 'DEBUG=0'
|
||||
build_type: release
|
||||
- libretro_build: 'DEBUG=1'
|
||||
build_type: debug
|
||||
- devkit_container: 'devkitpro/devkitppc:latest'
|
||||
target_name: ngc
|
||||
- devkit_container: 'devkitpro/devkitppc:latest'
|
||||
target_name: wii
|
||||
- devkit_container: 'devkitpro/devkitppc:latest'
|
||||
target_name: wiiu
|
||||
- devkit_container: 'devkitpro/devkita64:latest'
|
||||
target_name: switch
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.devkit_container }}
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Libretro build
|
||||
- name: Build libretro core
|
||||
run: make -C src/libretro ${{ matrix.libretro_build }} platform=${{ matrix.target_name }}
|
8
.github/workflows/macos-build.yml
vendored
8
.github/workflows/macos-build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: macOS Latest Build
|
||||
name: macOS Latest
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
@@ -55,3 +55,9 @@ jobs:
|
||||
name: Build libretro core
|
||||
run: >-
|
||||
nix-shell --command 'make -C src/libretro ${{ matrix.libretro_build }}'
|
||||
|
||||
# Run tests
|
||||
- if: matrix.build_options == 'default'
|
||||
name: Run tests
|
||||
run: >-
|
||||
nix-shell --command 'cd build && ctest -j --output-on-failure'
|
||||
|
2
.github/workflows/msys2-build.yml
vendored
2
.github/workflows/msys2-build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: MSYS2 Build
|
||||
name: MSYS2
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
14
.github/workflows/ubuntu-build.yml
vendored
14
.github/workflows/ubuntu-build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Ubuntu Latest Build
|
||||
name: Ubuntu Latest
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
@@ -45,6 +45,9 @@ jobs:
|
||||
run: >-
|
||||
bash installdeps; if [ "${{ matrix.build_compiler }}" = clang ]; then sudo apt -y install clang; fi
|
||||
|
||||
- name: Install xvfb
|
||||
run: sudo apt -y install xvfb
|
||||
|
||||
# CMake build
|
||||
- if: matrix.build_options != 'libretro'
|
||||
name: Configure CMake
|
||||
@@ -58,6 +61,11 @@ jobs:
|
||||
run: sudo ninja -C build install
|
||||
|
||||
# Libretro build
|
||||
- name: Build libretro core
|
||||
if: matrix.build_options == 'libretro'
|
||||
- if: matrix.build_options == 'libretro'
|
||||
name: Build libretro core
|
||||
run: make -C src/libretro ${{ matrix.libretro_build }}
|
||||
|
||||
# Run tests
|
||||
- if: matrix.build_options == 'default'
|
||||
name: Run tests
|
||||
run: cd build && xvfb-run ctest -j --output-on-failure
|
||||
|
7
.github/workflows/visual-studio-build.yml
vendored
7
.github/workflows/visual-studio-build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Visual Studio Build
|
||||
name: Visual Studio
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
@@ -53,3 +53,8 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
# Run tests
|
||||
- if: matrix.build_options == 'default' && matrix.msvc_arch != 'amd64_arm64'
|
||||
name: Run tests
|
||||
run: cd build && ctest -j --output-on-failure
|
||||
|
@@ -77,6 +77,26 @@ set(CMAKE_C_STANDARD_REQUIRED True)
|
||||
|
||||
project(VBA-M C CXX)
|
||||
|
||||
include(CTest)
|
||||
include(FetchContent)
|
||||
include(GNUInstallDirs)
|
||||
include(Architecture)
|
||||
include(Options)
|
||||
include(Toolchain)
|
||||
include(Dependencies)
|
||||
|
||||
# Configure gtest
|
||||
if(BUILD_TESTING)
|
||||
FetchContent_Declare(googletest
|
||||
URL https://github.com/google/googletest/archive/2d16ed055d09c3689d44b272adc097393de948a0.zip
|
||||
)
|
||||
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
include(GoogleTest)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_PREFIX_PATH AND (NOT ("$ENV{CMAKE_PREFIX_PATH}" STREQUAL "")))
|
||||
set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
|
||||
endif()
|
||||
@@ -97,17 +117,6 @@ if(NOT "$ENV{MSYSTEM_PREFIX}" STREQUAL "")
|
||||
set(MSYS ON)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(Options)
|
||||
include(Architecture)
|
||||
include(Toolchain)
|
||||
include(Dependencies)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
include(GitTagVersion)
|
||||
git_version(VBAM_VERSION VBAM_REVISION VBAM_VERSION_RELEASE)
|
||||
|
@@ -8,7 +8,12 @@ else()
|
||||
set(BUILD_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
option(ENABLE_SDL "Build the SDL port" ${BUILD_DEFAULT})
|
||||
set(ENABLE_SDL_DEFAULT ${BUILD_DEFAULT})
|
||||
if(WIN32 OR APPLE)
|
||||
set(ENABLE_SDL_DEFAULT OFF)
|
||||
endif()
|
||||
|
||||
option(ENABLE_SDL "Build the SDL port" ${ENABLE_SDL_DEFAULT})
|
||||
option(ENABLE_WX "Build the wxWidgets port" ${BUILD_DEFAULT})
|
||||
option(ENABLE_DEBUGGER "Enable the debugger" ON)
|
||||
option(ENABLE_ASAN "Enable -fsanitize=address by default. Requires debug build with GCC/Clang" OFF)
|
||||
@@ -146,7 +151,7 @@ set(ENABLE_FAUDIO_DEFAULT OFF)
|
||||
|
||||
find_package(FAudio QUIET)
|
||||
|
||||
if(FAudio_FOUND)
|
||||
if(FAudio_FOUND AND NOT (MINGW AND X86))
|
||||
set(ENABLE_FAUDIO_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
@@ -162,3 +167,5 @@ endif()
|
||||
if(TRANSLATIONS_ONLY AND (ENABLE_SDL OR ENABLE_WX))
|
||||
message(FATAL_ERROR "The SDL and wxWidgets ports can't be built when TRANSLATIONS_ONLY is enabled")
|
||||
endif()
|
||||
|
||||
option(GPG_SIGNATURES "Create GPG signatures for release files" OFF)
|
||||
|
@@ -51,6 +51,28 @@ if(NOT DEFINED VCPKG_TARGET_TRIPLET)
|
||||
message(STATUS "Inferred VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}")
|
||||
endif()
|
||||
|
||||
function(vcpkg_seconds)
|
||||
if(CMAKE_HOST_SYSTEM MATCHES Windows OR ((NOT DEFINED CMAKE_HOST_SYSTEM) AND WIN32))
|
||||
execute_process(
|
||||
COMMAND cmd /c echo %TIME:~0,8%
|
||||
OUTPUT_VARIABLE time
|
||||
)
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND date +%H:%M:%S
|
||||
OUTPUT_VARIABLE time
|
||||
)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING "${time}" 0 2 hours)
|
||||
string(SUBSTRING "${time}" 3 2 minutes)
|
||||
string(SUBSTRING "${time}" 6 2 secs)
|
||||
|
||||
math(EXPR seconds "(${hours} * 60 * 60) + (${minutes} * 60) + ${secs}")
|
||||
|
||||
set(seconds ${seconds} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(vcpkg_check_git_status git_status)
|
||||
# The VS vcpkg component cannot be written to without elevation.
|
||||
if(NOT git_status EQUAL 0 AND NOT VCPKG_ROOT MATCHES "Visual Studio")
|
||||
|
@@ -1,175 +0,0 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
doctest
|
||||
-----
|
||||
|
||||
This module defines a function to help use the doctest test framework.
|
||||
|
||||
The :command:`doctest_discover_tests` discovers tests by asking the compiled test
|
||||
executable to enumerate its tests. This does not require CMake to be re-run
|
||||
when tests change. However, it may not work in a cross-compiling environment,
|
||||
and setting test properties is less convenient.
|
||||
|
||||
This command is intended to replace use of :command:`add_test` to register
|
||||
tests, and will create a separate CTest test for each doctest test case. Note
|
||||
that this is in some cases less efficient, as common set-up and tear-down logic
|
||||
cannot be shared by multiple test cases executing in the same instance.
|
||||
However, it provides more fine-grained pass/fail information to CTest, which is
|
||||
usually considered as more beneficial. By default, the CTest test name is the
|
||||
same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
.. command:: doctest_discover_tests
|
||||
|
||||
Automatically add tests with CTest by querying the compiled test executable
|
||||
for available tests::
|
||||
|
||||
doctest_discover_tests(target
|
||||
[TEST_SPEC arg1...]
|
||||
[EXTRA_ARGS arg1...]
|
||||
[WORKING_DIRECTORY dir]
|
||||
[TEST_PREFIX prefix]
|
||||
[TEST_SUFFIX suffix]
|
||||
[PROPERTIES name1 value1...]
|
||||
[TEST_LIST var]
|
||||
)
|
||||
|
||||
``doctest_discover_tests`` sets up a post-build command on the test executable
|
||||
that generates the list of tests by parsing the output from running the test
|
||||
with the ``--list-test-cases`` argument. This ensures that the full
|
||||
list of tests is obtained. Since test discovery occurs at build time, it is
|
||||
not necessary to re-run CMake when the list of tests changes.
|
||||
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
|
||||
in order to function in a cross-compiling environment.
|
||||
|
||||
Additionally, setting properties on tests is somewhat less convenient, since
|
||||
the tests are not available at CMake time. Additional test properties may be
|
||||
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
|
||||
more fine-grained test control is needed, custom content may be provided
|
||||
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
|
||||
directory property. The set of discovered tests is made accessible to such a
|
||||
script via the ``<target>_TESTS`` variable.
|
||||
|
||||
The options are:
|
||||
|
||||
``target``
|
||||
Specifies the doctest executable, which must be a known CMake executable
|
||||
target. CMake will substitute the location of the built executable when
|
||||
running the test.
|
||||
|
||||
``TEST_SPEC arg1...``
|
||||
Specifies test cases, wildcarded test cases, tags and tag expressions to
|
||||
pass to the doctest executable with the ``--list-test-cases`` argument.
|
||||
|
||||
``EXTRA_ARGS arg1...``
|
||||
Any extra arguments to pass on the command line to each test case.
|
||||
|
||||
``WORKING_DIRECTORY dir``
|
||||
Specifies the directory in which to run the discovered test cases. If this
|
||||
option is not provided, the current binary directory is used.
|
||||
|
||||
``TEST_PREFIX prefix``
|
||||
Specifies a ``prefix`` to be prepended to the name of each discovered test
|
||||
case. This can be useful when the same test executable is being used in
|
||||
multiple calls to ``doctest_discover_tests()`` but with different
|
||||
``TEST_SPEC`` or ``EXTRA_ARGS``.
|
||||
|
||||
``TEST_SUFFIX suffix``
|
||||
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
|
||||
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
|
||||
be specified.
|
||||
|
||||
``PROPERTIES name1 value1...``
|
||||
Specifies additional properties to be set on all tests discovered by this
|
||||
invocation of ``doctest_discover_tests``.
|
||||
|
||||
``TEST_LIST var``
|
||||
Make the list of tests available in the variable ``var``, rather than the
|
||||
default ``<target>_TESTS``. This can be useful when the same test
|
||||
executable is being used in multiple calls to ``doctest_discover_tests()``.
|
||||
Note that this variable is only available in CTest.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
function(doctest_discover_tests TARGET)
|
||||
cmake_parse_arguments(
|
||||
""
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
if(NOT _WORKING_DIRECTORY)
|
||||
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if(NOT _TEST_LIST)
|
||||
set(_TEST_LIST ${TARGET}_TESTS)
|
||||
endif()
|
||||
|
||||
## Generate a unique name based on the extra arguments
|
||||
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
|
||||
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||
|
||||
# Define rule to generate test list for aforementioned test executable
|
||||
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
PROPERTY CROSSCOMPILING_EMULATOR
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-D "TEST_LIST=${_TEST_LIST}"
|
||||
-D "CTEST_FILE=${ctest_tests_file}"
|
||||
-P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE "${ctest_include_file}"
|
||||
"if(EXISTS \"${ctest_tests_file}\")\n"
|
||||
" include(\"${ctest_tests_file}\")\n"
|
||||
"else()\n"
|
||||
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.10)
|
||||
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||
set_property(DIRECTORY
|
||||
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
|
||||
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
|
||||
if(NOT ${test_include_file_set})
|
||||
set_property(DIRECTORY
|
||||
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_DOCTEST_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake
|
||||
)
|
@@ -1,81 +0,0 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
set(prefix "${TEST_PREFIX}")
|
||||
set(suffix "${TEST_SUFFIX}")
|
||||
set(spec ${TEST_SPEC})
|
||||
set(extra_args ${TEST_EXTRA_ARGS})
|
||||
set(properties ${TEST_PROPERTIES})
|
||||
set(script)
|
||||
set(suite)
|
||||
set(tests)
|
||||
|
||||
function(add_command NAME)
|
||||
set(_args "")
|
||||
foreach(_arg ${ARGN})
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
set(_args "${_args} ${_arg}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Run test executable to get list of available tests
|
||||
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
endif()
|
||||
|
||||
if("${spec}" MATCHES .)
|
||||
set(spec "--test-case=${spec}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases
|
||||
OUTPUT_VARIABLE output
|
||||
RESULT_VARIABLE result
|
||||
)
|
||||
if(NOT ${result} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" output "${output}")
|
||||
|
||||
# Parse output
|
||||
foreach(line ${output})
|
||||
if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==])
|
||||
continue()
|
||||
endif()
|
||||
set(test ${line})
|
||||
# use escape commas to handle properly test cases with commas inside the name
|
||||
string(REPLACE "," "\\," test_name ${test})
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${test}${suffix}"
|
||||
${TEST_EXECUTOR}
|
||||
"${TEST_EXECUTABLE}"
|
||||
"--test-case=${test_name}"
|
||||
${extra_args}
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${test}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
)
|
||||
list(APPEND tests "${prefix}${test}${suffix}")
|
||||
endforeach()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${CTEST_FILE}" "${script}")
|
1963
po/wxvbam/bg.po
1963
po/wxvbam/bg.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/br.po
1963
po/wxvbam/br.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/cs.po
1963
po/wxvbam/cs.po
File diff suppressed because it is too large
Load Diff
2027
po/wxvbam/de.po
2027
po/wxvbam/de.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/el.po
1963
po/wxvbam/el.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/es.po
1963
po/wxvbam/es.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/es_419.po
1963
po/wxvbam/es_419.po
File diff suppressed because it is too large
Load Diff
2504
po/wxvbam/fr_FR.po
2504
po/wxvbam/fr_FR.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/gl.po
1963
po/wxvbam/gl.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/he_IL.po
1963
po/wxvbam/he_IL.po
File diff suppressed because it is too large
Load Diff
1965
po/wxvbam/hu_HU.po
1965
po/wxvbam/hu_HU.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/id.po
1963
po/wxvbam/id.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/it_IT.po
1963
po/wxvbam/it_IT.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/ja.po
1963
po/wxvbam/ja.po
File diff suppressed because it is too large
Load Diff
1943
po/wxvbam/ja_JP.po
1943
po/wxvbam/ja_JP.po
File diff suppressed because it is too large
Load Diff
1965
po/wxvbam/ko_KR.po
1965
po/wxvbam/ko_KR.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/ms_MY.po
1963
po/wxvbam/ms_MY.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/nb.po
1963
po/wxvbam/nb.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/nl.po
1963
po/wxvbam/nl.po
File diff suppressed because it is too large
Load Diff
1965
po/wxvbam/pl_PL.po
1965
po/wxvbam/pl_PL.po
File diff suppressed because it is too large
Load Diff
1959
po/wxvbam/pt_BR.po
1959
po/wxvbam/pt_BR.po
File diff suppressed because it is too large
Load Diff
1943
po/wxvbam/pt_PT.po
1943
po/wxvbam/pt_PT.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/ru_RU.po
1963
po/wxvbam/ru_RU.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/sv.po
1963
po/wxvbam/sv.po
File diff suppressed because it is too large
Load Diff
1963
po/wxvbam/tr.po
1963
po/wxvbam/tr.po
File diff suppressed because it is too large
Load Diff
1965
po/wxvbam/uk.po
1965
po/wxvbam/uk.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1943
po/wxvbam/wxvbam.pot
1943
po/wxvbam/wxvbam.pot
File diff suppressed because it is too large
Load Diff
1953
po/wxvbam/zh-Hans.po
1953
po/wxvbam/zh-Hans.po
File diff suppressed because it is too large
Load Diff
1990
po/wxvbam/zh_CN.po
1990
po/wxvbam/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -136,3 +136,5 @@ if(ENABLE_LINK)
|
||||
PRIVATE ${NLS_LIBS}
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(test)
|
||||
|
@@ -5,7 +5,9 @@
|
||||
# Generate version_gen.h
|
||||
string(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+).*" "\\1,\\2,\\3,0" VBAM_WIN_VERSION "${VBAM_VERSION}")
|
||||
set(VBAM_GENERATED_VERSION_H ${VBAM_GENERATED_DIR}/core/base/version_gen.h)
|
||||
add_custom_target(vbam-core-base-generated
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${VBAM_GENERATED_VERSION_H}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DVBAM_GENERATED_VERSION_H=${VBAM_GENERATED_VERSION_H}
|
||||
-DVBAM_VERSION=${VBAM_VERSION}
|
||||
@@ -13,8 +15,6 @@ add_custom_target(vbam-core-base-generated
|
||||
-DVBAM_VERSION_RELEASE=${VBAM_VERSION_RELEASE}
|
||||
-DVBAM_WIN_VERSION=${VBAM_WIN_VERSION}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/build-version.cmake
|
||||
BYPRODUCTS
|
||||
${VBAM_GENERATED_VERSION_H}
|
||||
DEPENDS
|
||||
version_gen.h.in
|
||||
build-version.cmake
|
||||
@@ -22,8 +22,6 @@ add_custom_target(vbam-core-base-generated
|
||||
|
||||
add_library(vbam-core-base OBJECT)
|
||||
|
||||
add_dependencies(vbam-core-base vbam-core-base-generated)
|
||||
|
||||
target_sources(vbam-core-base
|
||||
PRIVATE
|
||||
file_util_common.cpp
|
||||
@@ -37,6 +35,7 @@ target_sources(vbam-core-base
|
||||
version.cpp
|
||||
|
||||
PUBLIC
|
||||
check.h
|
||||
array.h
|
||||
file_util.h
|
||||
image_util.h
|
||||
@@ -48,6 +47,8 @@ target_sources(vbam-core-base
|
||||
sound_driver.h
|
||||
system.h
|
||||
version.h
|
||||
# Generated file.
|
||||
${VBAM_GENERATED_VERSION_H}
|
||||
)
|
||||
|
||||
target_include_directories(vbam-core-base
|
||||
@@ -59,3 +60,5 @@ target_link_libraries(vbam-core-base
|
||||
PRIVATE vbam-fex stb-image
|
||||
PUBLIC ${ZLIB_LIBRARY}
|
||||
)
|
||||
|
||||
add_subdirectory(test)
|
||||
|
58
src/core/base/check.h
Normal file
58
src/core/base/check.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef VBAM_CORE_BASE_CHECK_H_
|
||||
#define VBAM_CORE_BASE_CHECK_H_
|
||||
|
||||
// This header defines a number of macros for checking conditions and crashing
|
||||
// the program if they are not met.
|
||||
// * VBAM_CHECK(condition) - crashes the program if the condition is not met.
|
||||
// * VBAM_NOTREACHED() - crashes the program if this line of code is reached.
|
||||
// In release builds, this macro also tells the compiler that this code path
|
||||
// is unreachable, which can help the compiler generate better code.
|
||||
// * VBAM_STRINGIFY(x) - converts the argument to a string literal.
|
||||
// While a number of other macros are defined in this file, they are not
|
||||
// intended for general use and should be avoided.
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
|
||||
// GCC/Clang.
|
||||
#define VBAM_IMMEDIATE_CRASH_DETAIL() __builtin_trap()
|
||||
#define VBAM_INTRINSIC_UNREACHABLE_DETAIL() __builtin_unreachable()
|
||||
|
||||
#elif defined(_MSC_VER) // defined(__GNUC__) || defined(__clang__)
|
||||
|
||||
// MSVC.
|
||||
#define VBAM_IMMEDIATE_CRASH_DETAIL() __debugbreak()
|
||||
#define VBAM_INTRINSIC_UNREACHABLE_DETAIL() __assume(0)
|
||||
|
||||
#else // defined(__GNUC__) || defined(__clang__)
|
||||
|
||||
#error "Unsupported compiler"
|
||||
|
||||
#endif // defined(__GNUC__) || defined(__clang__)
|
||||
|
||||
#define VBAM_STRINGIFY_DETAIL(x) #x
|
||||
#define VBAM_STRINGIFY(x) VBAM_STRINGIFY_DETAIL(x)
|
||||
|
||||
#define VBAM_REQUIRE_SEMICOLON_DETAIL() \
|
||||
static_assert(true, "Require a semicolon after macros invocation.")
|
||||
|
||||
#define VBAM_CHECK(condition) \
|
||||
if (!(condition)) { \
|
||||
fputs("CHECK failed at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) ": " #condition "\n", \
|
||||
stderr); \
|
||||
VBAM_IMMEDIATE_CRASH_DETAIL(); \
|
||||
} \
|
||||
VBAM_REQUIRE_SEMICOLON_DETAIL()
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
#define VBAM_NOTREACHED() \
|
||||
fputs("NOTREACHED code reached at " __FILE__ ":" VBAM_STRINGIFY(__LINE__) "\n", stderr); \
|
||||
VBAM_IMMEDIATE_CRASH_DETAIL()
|
||||
|
||||
#else // defined(DEBUG)
|
||||
|
||||
#define VBAM_NOTREACHED() VBAM_INTRINSIC_UNREACHABLE_DETAIL()
|
||||
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
#endif // VBAM_CORE_BASE_CHECK_H_
|
@@ -59,6 +59,7 @@ extern struct CoreOptions {
|
||||
bool speedHack = false;
|
||||
bool speedup = false;
|
||||
bool speedup_throttle_frame_skip = false;
|
||||
bool speedup_mute = true;
|
||||
int cheatsEnabled = 1;
|
||||
int cpuDisableSfx = 0;
|
||||
int cpuSaveType = 0;
|
||||
|
12
src/core/base/test/CMakeLists.txt
Normal file
12
src/core/base/test/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
# This defines the `vbam-core-base-test` library.
|
||||
|
||||
if(NOT BUILD_TESTING)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_library(vbam-core-base-test
|
||||
INTERFACE
|
||||
notreached.h)
|
||||
|
||||
target_link_libraries(vbam-core-base-test
|
||||
INTERFACE GTest::gtest)
|
22
src/core/base/test/notreached.h
Normal file
22
src/core/base/test/notreached.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef VBAM_CORE_BASE_TEST_NOTREACHED_H_
|
||||
#define VBAM_CORE_BASE_TEST_NOTREACHED_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
#define VBAM_EXPECT_NOTREACHED(statement) EXPECT_DEATH(statement, "NOTREACHED")
|
||||
|
||||
#else // defined(DEBUG)
|
||||
|
||||
// There is no way to test this in release builds as the compiler might optimize
|
||||
// this code path away. Eat the statement to avoid unused variable warnings.
|
||||
#define VBAM_EXPECT_NOTREACHED(statement) \
|
||||
if constexpr (false) { \
|
||||
statement; \
|
||||
} \
|
||||
static_assert(true, "")
|
||||
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
#endif // VBAM_CORE_BASE_TEST_NOTREACHED_H_
|
@@ -1,4 +1,7 @@
|
||||
#include "core/base/version.h"
|
||||
|
||||
#include "core/base/version_gen.h"
|
||||
|
||||
const std::string kVbamMainVersion = VBAM_CURRENT_VERSION;
|
||||
const std::string kVbamNameAndSubversion = VBAM_NAME_AND_SUBVERSION;
|
||||
const std::string kVbamVersion = VBAM_VERSION;
|
||||
|
@@ -3,7 +3,9 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/base/version_gen.h"
|
||||
// Main version information, generated by the build system.
|
||||
// e.g. "2.1.9"
|
||||
extern const std::string kVbamMainVersion;
|
||||
|
||||
// Full version information, generated by the build system.
|
||||
// e.g. "VisualBoyAdvance-M 2.1.9-316e4a43 msvc-316e4a43"
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#include "core/gb/gb.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/file_util.h"
|
||||
#include "core/base/message.h"
|
||||
#include "core/base/sizes.h"
|
||||
@@ -291,8 +291,7 @@ bool gbInitializeRom(size_t romSize) {
|
||||
switch (g_gbCartData.validity()) {
|
||||
case gbCartData::Validity::kValid:
|
||||
case gbCartData::Validity::kUninitialized:
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
break;
|
||||
case gbCartData::Validity::kSizeTooSmall:
|
||||
systemMessage(MSG_UNSUPPORTED_ROM_SIZE,
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/sizes.h"
|
||||
|
||||
namespace {
|
||||
@@ -62,7 +62,7 @@ char byte_to_char(uint8_t byte) {
|
||||
} else if (byte < 16) {
|
||||
return 'A' + (byte - 10);
|
||||
} else {
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ bool is_valid_manufacturer_code(const std::string& manufacturer_code) {
|
||||
constexpr size_t kHeaderGlobalChecksumAdress = 0x14e;
|
||||
|
||||
uint16_t get_rom_checksum(const uint8_t* romData, size_t romDataSize) {
|
||||
assert(romData);
|
||||
VBAM_CHECK(romData);
|
||||
|
||||
uint16_t checksum = 0;
|
||||
for (size_t i = 0; i < romDataSize; i++) {
|
||||
@@ -160,7 +160,7 @@ constexpr size_t kHeaderChecksumEndAdress = 0x14c;
|
||||
} // namespace
|
||||
|
||||
gbCartData::gbCartData(const uint8_t* romData, size_t romDataSize) {
|
||||
assert(romData);
|
||||
VBAM_CHECK(romData);
|
||||
|
||||
if (romDataSize < sizeof(gbRomHeader)) {
|
||||
validity_ = Validity::kSizeTooSmall;
|
||||
|
@@ -3845,6 +3845,8 @@ void CPULoop(int ticks)
|
||||
bool turbo_button_pressed = (joy >> 10) & 1;
|
||||
#ifndef __LIBRETRO__
|
||||
static uint32_t last_throttle;
|
||||
static bool current_volume_saved = false;
|
||||
static float current_volume;
|
||||
|
||||
if (turbo_button_pressed) {
|
||||
if (coreOptions.speedup_frame_skip)
|
||||
@@ -3859,11 +3861,24 @@ void CPULoop(int ticks)
|
||||
if (coreOptions.speedup_throttle_frame_skip)
|
||||
framesToSkip += static_cast<int>(std::ceil(double(coreOptions.speedup_throttle) / 100.0) - 1);
|
||||
}
|
||||
|
||||
if (coreOptions.speedup_mute && !current_volume_saved) {
|
||||
current_volume = soundGetVolume();
|
||||
current_volume_saved = true;
|
||||
soundSetVolume(0);
|
||||
}
|
||||
else if (speedup_throttle_set) {
|
||||
}
|
||||
else {
|
||||
if (current_volume_saved) {
|
||||
soundSetVolume(current_volume);
|
||||
current_volume_saved = false;
|
||||
}
|
||||
|
||||
if (speedup_throttle_set) {
|
||||
soundSetThrottle(DowncastU16(last_throttle));
|
||||
speedup_throttle_set = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (turbo_button_pressed)
|
||||
framesToSkip = 9;
|
||||
|
13
src/core/test/CMakeLists.txt
Normal file
13
src/core/test/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# This defines the `vbam-core-fake` library, which is used for providing a fake
|
||||
# implementation of the core library for testing purposes.
|
||||
|
||||
if(NOT BUILD_TESTING)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_library(vbam-core-fake OBJECT)
|
||||
|
||||
target_sources(vbam-core-fake
|
||||
PRIVATE
|
||||
fake_core.cpp
|
||||
)
|
94
src/core/test/fake_core.cpp
Normal file
94
src/core/test/fake_core.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "core/base/system.h"
|
||||
|
||||
void systemMessage(int, const char*, ...) {}
|
||||
|
||||
void log(const char*, ...) {}
|
||||
|
||||
bool systemPauseOnFrame() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void systemGbPrint(uint8_t*, int, int, int, int, int) {}
|
||||
|
||||
void systemScreenCapture(int) {}
|
||||
|
||||
void systemDrawScreen() {}
|
||||
|
||||
void systemSendScreen() {}
|
||||
|
||||
bool systemReadJoypads() {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t systemReadJoypad(int) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t systemGetClock() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void systemSetTitle(const char*) {}
|
||||
|
||||
std::unique_ptr<SoundDriver> systemSoundInit() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void systemOnWriteDataToSoundBuffer(const uint16_t* /*finalWave*/, int /*length*/) {}
|
||||
|
||||
void systemOnSoundShutdown() {}
|
||||
|
||||
void systemScreenMessage(const char*) {}
|
||||
|
||||
void systemUpdateMotionSensor() {}
|
||||
|
||||
int systemGetSensorX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int systemGetSensorY() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int systemGetSensorZ() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t systemGetSensorDarkness() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void systemCartridgeRumble(bool) {}
|
||||
|
||||
void systemPossibleCartridgeRumble(bool) {}
|
||||
|
||||
void updateRumbleFrame() {}
|
||||
|
||||
bool systemCanChangeSoundQuality() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void systemShowSpeed(int) {}
|
||||
|
||||
void system10Frames() {}
|
||||
|
||||
void systemFrame() {}
|
||||
|
||||
void systemGbBorderOn() {}
|
||||
|
||||
void (*dbgOutput)(const char* s, uint32_t addr);
|
||||
void (*dbgSignal)(int sig, int number);
|
||||
|
||||
uint16_t systemColorMap16[0x10000];
|
||||
uint32_t systemColorMap32[0x10000];
|
||||
uint16_t systemGbPalette[24];
|
||||
int systemRedShift;
|
||||
int systemGreenShift;
|
||||
int systemBlueShift;
|
||||
int systemColorDepth;
|
||||
int systemVerbose;
|
||||
int systemFrameSkip;
|
||||
int systemSaveUpdateCounter;
|
||||
int systemSpeed;
|
||||
|
||||
int emulating = 0;
|
@@ -311,7 +311,7 @@ else ifeq ($(platform), wiiu)
|
||||
CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT)
|
||||
AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
|
||||
ENDIANNESS_DEFINES += -DMSB_FIRST -DWORDS_BIGENDIAN=1
|
||||
PLATFORM_DEFINES += -DGEKKO -DWIIU -DHW_RVL -mwup -mcpu=750 -meabi -mhard-float -D__ppc__
|
||||
PLATFORM_DEFINES += -DGEKKO -DWIIU -DHW_WUP -mcpu=750 -meabi -mhard-float -D__ppc__
|
||||
PLATFORM_DEFINES += -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int
|
||||
STATIC_LINKING=1
|
||||
TILED_RENDERING=1
|
||||
@@ -325,14 +325,15 @@ else ifeq ($(platform), libnx)
|
||||
-fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec -Wl,--allow-multiple-definition -specs=$(LIBNX)/switch.specs
|
||||
CFLAGS += $(INCDIRS)
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__ -DHAVE_LIBNX
|
||||
CXXFLAGS := $(ASFLAGS) $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
||||
CFLAGS += -std=gnu11
|
||||
CXXFLAGS := $(ASFLAGS) $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
STATIC_LINKING=1
|
||||
|
||||
# Nintendo Switch (libtransistor)
|
||||
# Nintendo Switch (devkitpro)
|
||||
else ifeq ($(platform), switch)
|
||||
TARGET := $(TARGET_NAME)_libretro_$(platform).a
|
||||
include $(LIBTRANSISTOR_HOME)/libtransistor.mk
|
||||
CC = $(DEVKITPRO)/devkitA64/bin/aarch64-none-elf-gcc$(EXE_EXT)
|
||||
CXX = $(DEVKITPRO)/devkitA64/bin/aarch64-none-elf-g++$(EXE_EXT)
|
||||
AR = $(DEVKITPRO)/devkitA64/bin/aarch64-none-elf-ar$(EXE_EXT)
|
||||
STATIC_LINKING=1
|
||||
TILED_RENDERING=1
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -12,6 +11,7 @@
|
||||
|
||||
#include "components/filters_agb/filters_agb.h"
|
||||
#include "components/filters_interframe/interframe.h"
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/system.h"
|
||||
#include "core/base/file_util.h"
|
||||
#include "core/base/sizes.h"
|
||||
@@ -177,8 +177,7 @@ static void* gb_rtcdata_prt(void)
|
||||
case gbCartData::MapperType::kGameGenie:
|
||||
case gbCartData::MapperType::kGameShark:
|
||||
case gbCartData::MapperType::kUnknown:
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
@@ -205,8 +204,7 @@ static size_t gb_rtcdata_size(void)
|
||||
case gbCartData::MapperType::kGameGenie:
|
||||
case gbCartData::MapperType::kGameShark:
|
||||
case gbCartData::MapperType::kUnknown:
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@@ -272,8 +270,7 @@ static void gbInitRTC(void)
|
||||
case gbCartData::MapperType::kGameGenie:
|
||||
case gbCartData::MapperType::kGameShark:
|
||||
case gbCartData::MapperType::kUnknown:
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1456,8 +1453,7 @@ void retro_run(void)
|
||||
case gbCartData::MapperType::kGameGenie:
|
||||
case gbCartData::MapperType::kGameShark:
|
||||
case gbCartData::MapperType::kUnknown:
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
/* Initialize RTC using local time if needed */
|
||||
|
@@ -98,7 +98,8 @@ enum named_opts
|
||||
OPT_SOUND_FILTERING,
|
||||
OPT_SPEEDUP_THROTTLE,
|
||||
OPT_SPEEDUP_FRAME_SKIP,
|
||||
OPT_NO_SPEEDUP_THROTTLE_FRAME_SKIP
|
||||
OPT_NO_SPEEDUP_THROTTLE_FRAME_SKIP,
|
||||
OPT_NO_SPEEDUP_MUTE
|
||||
};
|
||||
|
||||
#define SOUND_MAX_VOLUME 2.0
|
||||
@@ -232,6 +233,7 @@ struct option argOptions[] = {
|
||||
{ "speedup-throttle", required_argument, 0, OPT_SPEEDUP_THROTTLE },
|
||||
{ "speedup-frame-skip", required_argument, 0, OPT_SPEEDUP_FRAME_SKIP },
|
||||
{ "no-speedup-throttle-frame-skip", no_argument, 0, OPT_NO_SPEEDUP_THROTTLE_FRAME_SKIP },
|
||||
{ "no-speedup-mute", no_argument, 0, OPT_NO_SPEEDUP_MUTE },
|
||||
{ "use-bios", no_argument, &coreOptions.useBios, 1 },
|
||||
{ "verbose", required_argument, 0, 'v' },
|
||||
{ "win-gb-printer-enabled", no_argument, &coreOptions.winGbPrinterEnabled, 1 },
|
||||
@@ -347,6 +349,7 @@ void LoadConfig()
|
||||
coreOptions.speedup_throttle = ReadPref("speedupThrottle", 100);
|
||||
coreOptions.speedup_frame_skip = ReadPref("speedupFrameSkip", 9);
|
||||
coreOptions.speedup_throttle_frame_skip = ReadPref("speedupThrottleFrameSkip", 0);
|
||||
coreOptions.speedup_mute = ReadPref("speedupMute", 1);
|
||||
coreOptions.useBios = ReadPrefHex("useBiosGBA");
|
||||
coreOptions.winGbPrinterEnabled = ReadPref("gbPrinter", 0);
|
||||
|
||||
@@ -990,6 +993,9 @@ int ReadOpts(int argc, char ** argv)
|
||||
case OPT_NO_SPEEDUP_THROTTLE_FRAME_SKIP:
|
||||
coreOptions.speedup_throttle_frame_skip = false;
|
||||
break;
|
||||
case OPT_NO_SPEEDUP_MUTE:
|
||||
coreOptions.speedup_mute = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return op;
|
||||
|
@@ -13,22 +13,6 @@ set(VBAM_WX_COMMON
|
||||
background-input.cpp
|
||||
background-input.h
|
||||
cmdevents.cpp
|
||||
config/game-control.cpp
|
||||
config/game-control.h
|
||||
config/internal/option-internal.cpp
|
||||
config/internal/option-internal.h
|
||||
config/internal/shortcuts-internal.cpp
|
||||
config/internal/shortcuts-internal.h
|
||||
config/option-id.h
|
||||
config/option-observer.cpp
|
||||
config/option-observer.h
|
||||
config/option-proxy.h
|
||||
config/option.cpp
|
||||
config/option.h
|
||||
config/shortcuts.cpp
|
||||
config/shortcuts.h
|
||||
config/user-input.cpp
|
||||
config/user-input.h
|
||||
dialogs/accel-config.cpp
|
||||
dialogs/accel-config.h
|
||||
dialogs/base-dialog.cpp
|
||||
@@ -47,6 +31,8 @@ set(VBAM_WX_COMMON
|
||||
dialogs/joypad-config.h
|
||||
dialogs/sound-config.cpp
|
||||
dialogs/sound-config.h
|
||||
dialogs/speedup-config.cpp
|
||||
dialogs/speedup-config.h
|
||||
drawing.h
|
||||
extra-translations.cpp
|
||||
gfxviewers.cpp
|
||||
@@ -56,39 +42,14 @@ set(VBAM_WX_COMMON
|
||||
opts.h
|
||||
panel.cpp
|
||||
rpi.h
|
||||
strutils.cpp
|
||||
strutils.h
|
||||
sys.cpp
|
||||
viewers.cpp
|
||||
viewsupt.cpp
|
||||
viewsupt.h
|
||||
wayland.cpp
|
||||
wayland.h
|
||||
# from external source with minor modifications
|
||||
widgets/checkedlistctrl.cpp
|
||||
widgets/checkedlistctrl.h
|
||||
widgets/dpi-support.h
|
||||
widgets/group-check-box.cpp
|
||||
widgets/group-check-box.h
|
||||
widgets/keep-on-top-styler.cpp
|
||||
widgets/keep-on-top-styler.h
|
||||
widgets/option-validator.cpp
|
||||
widgets/option-validator.h
|
||||
widgets/render-plugin.cpp
|
||||
widgets/render-plugin.h
|
||||
widgets/user-input-ctrl.cpp
|
||||
widgets/user-input-ctrl.h
|
||||
widgets/sdljoy.cpp
|
||||
widgets/sdljoy.h
|
||||
widgets/utils.cpp
|
||||
widgets/utils.h
|
||||
widgets/webupdatedef.h
|
||||
widgets/wxmisc.h
|
||||
widgets/wxmisc.cpp
|
||||
wxhead.h
|
||||
wxlogdebug.h
|
||||
wxutil.cpp
|
||||
wxutil.h
|
||||
wxvbam.cpp
|
||||
wxvbam.h
|
||||
x11keymap.h
|
||||
@@ -98,7 +59,6 @@ set(VBAM_WX_COMMON
|
||||
${VBAM_GENERATED_DIR}/wx/builtin-over.h
|
||||
${VBAM_GENERATED_DIR}/wx/cmdhandlers.h
|
||||
${VBAM_GENERATED_DIR}/wx/cmd-evtable.h
|
||||
${VBAM_GENERATED_DIR}/wx/cmdtab.cpp
|
||||
)
|
||||
|
||||
if(NOT ZIP_PROGRAM)
|
||||
@@ -179,6 +139,158 @@ if(TRANSLATIONS_ONLY)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# wxWidgets configuration.
|
||||
set(wxWidgets_USE_UNICODE ON)
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$")
|
||||
set(wxWidgets_USE_DEBUG ON) # noop if wx is compiled with --disable-debug, like in Mac Homebrew atm
|
||||
endif()
|
||||
if(VBAM_STATIC)
|
||||
set(wxWidgets_USE_STATIC ON)
|
||||
endif()
|
||||
|
||||
unset(wx_find_extra)
|
||||
if(CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg")
|
||||
set(wx_find_extra NO_DEFAULT_PATH)
|
||||
set(wxWidgets_DIR "${VCPKG_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/share/wxwidgets")
|
||||
endif()
|
||||
|
||||
set(ENABLE_OPENGL TRUE)
|
||||
find_package(wxWidgets COMPONENTS xrc xml html adv net core base gl ${wx_find_extra})
|
||||
|
||||
if(NOT wxWidgets_FOUND)
|
||||
find_package(wxWidgets COMPONENTS xrc xml html adv net core base ${wx_find_extra} REQUIRED)
|
||||
set(ENABLE_OPENGL FALSE)
|
||||
endif()
|
||||
|
||||
# Find OpenAL (required).
|
||||
find_package(OpenAL REQUIRED)
|
||||
|
||||
# Workaround of static liblzma not being found on MSYS2.
|
||||
if(VBAM_STATIC AND MSYS)
|
||||
unset(cleaned_up_wx_libs)
|
||||
foreach(lib ${wxWidgets_LIBRARIES})
|
||||
if(lib STREQUAL "-llzma")
|
||||
set(lib "liblzma.a")
|
||||
endif()
|
||||
|
||||
list(APPEND cleaned_up_wx_libs "${lib}")
|
||||
endforeach()
|
||||
|
||||
set(wxWidgets_LIBRARIES "${cleaned_up_wx_libs}")
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES ${wxWidgets_LIBRARIES})
|
||||
list(APPEND CMAKE_REQUIRED_INCLUDES ${wxWidgets_INCLUDE_DIRS})
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS ${wxWidgets_CXX_FLAGS})
|
||||
foreach(DEF ${wxWidgets_DEFINITIONS})
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D${DEF}")
|
||||
endforeach()
|
||||
|
||||
# Configure common settings for wx-based targets, like linking, include
|
||||
# directories, compile options, and definitions.
|
||||
function(configure_wx_target target)
|
||||
get_target_property(target_type ${target} TYPE)
|
||||
if(target_type STREQUAL "EXECUTABLE")
|
||||
set(target_is_executable TRUE)
|
||||
else()
|
||||
set(target_is_executable FALSE)
|
||||
endif()
|
||||
|
||||
function(_add_link_libraries)
|
||||
if(${target_is_executable})
|
||||
target_link_libraries(${target} ${ARGN})
|
||||
else()
|
||||
target_link_libraries(${target} PUBLIC ${ARGN})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_add_include_directories)
|
||||
if(${target_is_executable})
|
||||
target_include_directories(${target} PRIVATE ${ARGN})
|
||||
else()
|
||||
target_include_directories(${target} PUBLIC ${ARGN})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_add_compile_options)
|
||||
if(${target_is_executable})
|
||||
target_compile_options(${target} PRIVATE ${ARGN})
|
||||
else()
|
||||
target_compile_options(${target} PUBLIC ${ARGN})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_add_compile_definitions)
|
||||
if(${target_is_executable})
|
||||
target_compile_definitions(${target} PRIVATE ${ARGN})
|
||||
else()
|
||||
target_compile_definitions(${target} PUBLIC ${ARGN})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Core emulator.
|
||||
_add_link_libraries(vbam-core)
|
||||
|
||||
# Nonstd.
|
||||
_add_link_libraries(nonstd-lib)
|
||||
_add_include_directories(${NONSTD_INCLUDE_DIR})
|
||||
|
||||
# wxWidgets.
|
||||
_add_link_libraries(${wxWidgets_LIBRARIES})
|
||||
_add_include_directories(${wxWidgets_INCLUDE_DIRS})
|
||||
_add_compile_options(${wxWidgets_CXX_FLAGS})
|
||||
_add_compile_definitions(${wxWidgets_DEFINITIONS})
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$")
|
||||
_add_compile_definitions(${wxWidgets_DEFINITIONS_DEBUG})
|
||||
endif()
|
||||
|
||||
# OpenAL.
|
||||
if(OPENAL_STATIC)
|
||||
_add_compile_definitions(AL_LIBTYPE_STATIC)
|
||||
endif()
|
||||
_add_include_directories(${OPENAL_INCLUDE_DIR})
|
||||
_add_link_libraries(${OPENAL_LIBRARY})
|
||||
|
||||
# XAudio2.
|
||||
if(ENABLE_XAUDIO2)
|
||||
_add_compile_definitions(VBAM_ENABLE_XAUDIO2)
|
||||
endif()
|
||||
|
||||
# FAudio.
|
||||
if(ENABLE_FAUDIO)
|
||||
_add_compile_definitions(VBAM_ENABLE_FAUDIO)
|
||||
if(MSVC)
|
||||
_add_link_libraries(FAudio::FAudio)
|
||||
else()
|
||||
_add_link_libraries(FAudio)
|
||||
if(WIN32)
|
||||
_add_link_libraries(dxguid uuid winmm ole32 advapi32 user32 mfplat mfreadwrite mfuuid propsys)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Direct3D.
|
||||
if(NOT ENABLE_DIRECT3D)
|
||||
_add_compile_definitions(NO_D3D)
|
||||
endif()
|
||||
|
||||
# SDL2.
|
||||
_add_link_libraries(${VBAM_SDL2_LIBS})
|
||||
|
||||
# OpenGL.
|
||||
if(ENABLE_OPENGL)
|
||||
_add_link_libraries(${OPENGL_LIBRARIES})
|
||||
else()
|
||||
_add_compile_definitions(NO_OGL)
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
# Sub-projects.
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(widgets)
|
||||
|
||||
set(VBAM_ICON visualboyadvance-m.icns)
|
||||
set(VBAM_ICON_PATH ${CMAKE_CURRENT_SOURCE_DIR}/icons/${VBAM_ICON})
|
||||
|
||||
@@ -189,19 +301,17 @@ add_executable(
|
||||
)
|
||||
|
||||
target_sources(visualboyadvance-m PRIVATE ${VBAM_WX_COMMON} ${VBAM_ICON_PATH})
|
||||
target_include_directories(visualboyadvance-m PRIVATE ${NONSTD_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS})
|
||||
target_include_directories(visualboyadvance-m PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(
|
||||
visualboyadvance-m
|
||||
nonstd-lib
|
||||
vbam-core
|
||||
vbam-components-draw-text
|
||||
vbam-components-filters
|
||||
vbam-components-filters-agb
|
||||
vbam-components-filters-interframe
|
||||
vbam-components-user-config
|
||||
${OPENGL_LIBRARIES}
|
||||
${VBAM_SDL2_LIBS}
|
||||
vbam-wx-config
|
||||
vbam-wx-widgets
|
||||
)
|
||||
|
||||
# adjust link command when making a static binary for gcc
|
||||
@@ -279,11 +389,6 @@ endif()
|
||||
if(APPLE)
|
||||
target_sources(visualboyadvance-m PRIVATE
|
||||
macsupport.mm
|
||||
widgets/dpi-support-mac.mm
|
||||
)
|
||||
else()
|
||||
target_sources(visualboyadvance-m PRIVATE
|
||||
widgets/dpi-support.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -308,82 +413,17 @@ if(CMAKE_COMPILER_IS_GNUCXX AND VBAM_STATIC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# OpenAL.
|
||||
find_package(OpenAL REQUIRED)
|
||||
if(OPENAL_STATIC)
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE AL_LIBTYPE_STATIC)
|
||||
endif()
|
||||
target_include_directories(visualboyadvance-m PRIVATE ${OPENAL_INCLUDE_DIR})
|
||||
target_link_libraries(visualboyadvance-m ${OPENAL_LIBRARY})
|
||||
|
||||
# XAudio2.
|
||||
if(ENABLE_XAUDIO2)
|
||||
target_sources(visualboyadvance-m PRIVATE audio/internal/xaudio2.cpp)
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE VBAM_ENABLE_XAUDIO2)
|
||||
endif()
|
||||
|
||||
# Direct3D.
|
||||
if(NOT ENABLE_DIRECT3D)
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE NO_D3D)
|
||||
endif()
|
||||
|
||||
# FAudio.
|
||||
if(ENABLE_FAUDIO)
|
||||
target_sources(visualboyadvance-m PRIVATE audio/internal/faudio.cpp)
|
||||
target_link_libraries(visualboyadvance-m FAudio::FAudio)
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE VBAM_ENABLE_FAUDIO)
|
||||
endif()
|
||||
|
||||
# wxWidgets.
|
||||
set(wxWidgets_USE_UNICODE ON)
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$")
|
||||
set(wxWidgets_USE_DEBUG ON) # noop if wx is compiled with --disable-debug, like in Mac Homebrew atm
|
||||
endif()
|
||||
if(VBAM_STATIC)
|
||||
set(wxWidgets_USE_STATIC ON)
|
||||
endif()
|
||||
|
||||
unset(wx_find_extra)
|
||||
if(CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg")
|
||||
set(wx_find_extra NO_DEFAULT_PATH)
|
||||
set(wxWidgets_DIR "${VCPKG_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/share/wxwidgets")
|
||||
endif()
|
||||
|
||||
find_package(wxWidgets COMPONENTS xrc xml html adv net core base gl ${wx_find_extra})
|
||||
|
||||
if(NOT wxWidgets_FOUND)
|
||||
find_package(wxWidgets COMPONENTS xrc xml html adv net core base ${wx_find_extra} REQUIRED)
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE NO_OGL)
|
||||
endif()
|
||||
|
||||
# Workaround of static liblzma not being found on MSYS2.
|
||||
if(VBAM_STATIC AND MSYS)
|
||||
unset(cleaned_up_wx_libs)
|
||||
foreach(lib ${wxWidgets_LIBRARIES})
|
||||
if(lib STREQUAL "-llzma")
|
||||
set(lib "liblzma.a")
|
||||
endif()
|
||||
|
||||
list(APPEND cleaned_up_wx_libs "${lib}")
|
||||
endforeach()
|
||||
|
||||
set(wxWidgets_LIBRARIES "${cleaned_up_wx_libs}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(visualboyadvance-m ${wxWidgets_LIBRARIES})
|
||||
target_include_directories(visualboyadvance-m PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
||||
target_compile_options(visualboyadvance-m PRIVATE ${wxWidgets_CXX_FLAGS})
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE ${wxWidgets_DEFINITIONS})
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$")
|
||||
target_compile_definitions(visualboyadvance-m PRIVATE ${wxWidgets_DEFINITIONS_DEBUG})
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES ${wxWidgets_LIBRARIES})
|
||||
list(APPEND CMAKE_REQUIRED_INCLUDES ${wxWidgets_INCLUDE_DIRS})
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS ${wxWidgets_CXX_FLAGS})
|
||||
foreach(DEF ${wxWidgets_DEFINITIONS})
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D${DEF}")
|
||||
endforeach()
|
||||
configure_wx_target(visualboyadvance-m)
|
||||
|
||||
# we make some direct gtk/gdk calls on linux and such
|
||||
# so need to link the gtk that wx was built with
|
||||
@@ -593,24 +633,7 @@ add_custom_command(
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/src/vba-over.ini ${BIN2C}
|
||||
)
|
||||
|
||||
# I don't like duplicating/triplicating code, so I only declare
|
||||
# event handlers once, and copy them in other places they are needed
|
||||
# all using portable cmake code
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${VBAM_GENERATED_DIR}/wx/cmdtab.cpp
|
||||
${VBAM_GENERATED_DIR}/wx/cmdhandlers.h
|
||||
${VBAM_GENERATED_DIR}/wx/cmd-evtable.h
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -D OUTDIR=${VBAM_GENERATED_DIR}/wx/ -P copy-events.cmake
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS
|
||||
cmdevents.cpp
|
||||
copy-events.cmake
|
||||
)
|
||||
|
||||
set(VBAM_LOCALIZABLE_FILES ${VBAM_WX_COMMON})
|
||||
set(VBAM_LOCALIZABLE_FILES ${VBAM_WX_COMMON} ${VBAM_LOCALIZABLE_WX_CONFIG_FILES})
|
||||
list(APPEND VBAM_LOCALIZABLE_FILES
|
||||
audio/internal/dsound.cpp
|
||||
audio/internal/faudio.cpp
|
||||
@@ -805,7 +828,7 @@ if(UPSTREAM_RELEASE AND WIN32)
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
if(GPG_KEYS)
|
||||
if(GPG_SIGNATURES AND GPG_KEYS)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/translations.zip.asc
|
||||
COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_BINARY_DIR}/translations.zip.asc
|
||||
@@ -982,10 +1005,6 @@ install(
|
||||
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
if(BUILD_TESTING AND (NOT CMAKE_CROSSCOMPILING))
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# Installation scripts.
|
||||
install(
|
||||
PROGRAMS ${PROJECT_BINARY_DIR}/visualboyadvance-m${CMAKE_EXECUTABLE_SUFFIX}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
#include "wx/audio/audio.h"
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "wx/audio/internal/openal.h"
|
||||
|
||||
#if defined(__WXMSW__)
|
||||
#include "wx/audio/internal/dsound.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(VBAM_ENABLE_FAUDIO)
|
||||
#include "wx/audio/internal/faudio.h"
|
||||
#endif
|
||||
@@ -39,8 +39,7 @@ std::vector<AudioDevice> EnumerateAudioDevices(const config::AudioApi& audio_api
|
||||
|
||||
case config::AudioApi::kLast:
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -67,8 +66,7 @@ std::unique_ptr<SoundDriver> CreateSoundDriver(const config::AudioApi& api) {
|
||||
|
||||
case config::AudioApi::kLast:
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "wx/audio/internal/faudio.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
@@ -18,9 +16,11 @@
|
||||
#include <wx/log.h>
|
||||
#include <wx/translation.h>
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/system.h"
|
||||
#include "core/gba/gbaGlobals.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/strutils.h"
|
||||
|
||||
namespace audio {
|
||||
namespace internal {
|
||||
@@ -28,7 +28,7 @@ namespace internal {
|
||||
namespace {
|
||||
|
||||
int FAGetDev(FAudio* fa) {
|
||||
const wxString& audio_device = OPTION(kSoundAudioDevice);
|
||||
const wxString audio_device = OPTION(kSoundAudioDevice).Get();
|
||||
if (audio_device.empty()) {
|
||||
// Just use the default device.
|
||||
return 0;
|
||||
@@ -48,7 +48,10 @@ int FAGetDev(FAudio* fa) {
|
||||
if (hr != 0) {
|
||||
continue;
|
||||
}
|
||||
const wxString device_id(reinterpret_cast<wchar_t*>(dd.DeviceID));
|
||||
const std::vector<uint8_t> device_id_u8 =
|
||||
config::utf16_to_utf8(reinterpret_cast<uint16_t*>(dd.DeviceID));
|
||||
const wxString device_id = wxString::FromUTF8(
|
||||
reinterpret_cast<const char*>(device_id_u8.data()), device_id_u8.size());
|
||||
if (audio_device == device_id) {
|
||||
return i;
|
||||
}
|
||||
@@ -120,8 +123,6 @@ public:
|
||||
FAudio_Output();
|
||||
~FAudio_Output();
|
||||
|
||||
void device_change();
|
||||
|
||||
private:
|
||||
void close();
|
||||
|
||||
@@ -176,7 +177,7 @@ void FAudio_Output::close() {
|
||||
|
||||
if (sVoice) {
|
||||
if (playing) {
|
||||
assert(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
|
||||
VBAM_CHECK(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
|
||||
}
|
||||
|
||||
FAudioVoice_DestroyVoice(sVoice);
|
||||
@@ -194,10 +195,6 @@ void FAudio_Output::close() {
|
||||
}
|
||||
}
|
||||
|
||||
void FAudio_Output::device_change() {
|
||||
device_changed = true;
|
||||
}
|
||||
|
||||
bool FAudio_Output::init(long sampleRate) {
|
||||
if (failed || initialized)
|
||||
return false;
|
||||
@@ -259,7 +256,7 @@ bool FAudio_Output::init(long sampleRate) {
|
||||
if (OPTION(kSoundUpmix)) {
|
||||
// set up stereo upmixing
|
||||
FAudioDeviceDetails dd{};
|
||||
assert(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
|
||||
VBAM_CHECK(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
|
||||
std::vector<float> matrix(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels);
|
||||
|
||||
bool matrixAvailable = true;
|
||||
@@ -353,12 +350,12 @@ bool FAudio_Output::init(long sampleRate) {
|
||||
if (matrixAvailable) {
|
||||
hr = FAudioVoice_SetOutputMatrix(sVoice, nullptr, 2, dd.OutputFormat.Format.nChannels,
|
||||
matrix.data(), FAUDIO_DEFAULT_CHANNELS);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
}
|
||||
}
|
||||
|
||||
hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
playing = true;
|
||||
currentBuffer = 0;
|
||||
device_changed = false;
|
||||
@@ -380,7 +377,7 @@ void FAudio_Output::write(uint16_t* finalWave, int) {
|
||||
}
|
||||
|
||||
FAudioSourceVoice_GetState(sVoice, &vState, flags);
|
||||
assert(vState.BuffersQueued <= buffer_count_);
|
||||
VBAM_CHECK(vState.BuffersQueued <= buffer_count_);
|
||||
|
||||
if (vState.BuffersQueued < buffer_count_) {
|
||||
if (vState.BuffersQueued == 0) {
|
||||
@@ -414,7 +411,7 @@ void FAudio_Output::write(uint16_t* finalWave, int) {
|
||||
currentBuffer++;
|
||||
currentBuffer %= (buffer_count_ + 1); // + 1 because we need one temporary buffer
|
||||
[[maybe_unused]] uint32_t hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, nullptr);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
}
|
||||
|
||||
void FAudio_Output::pause() {
|
||||
@@ -423,7 +420,7 @@ void FAudio_Output::pause() {
|
||||
|
||||
if (playing) {
|
||||
[[maybe_unused]] uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
playing = false;
|
||||
}
|
||||
}
|
||||
@@ -434,7 +431,7 @@ void FAudio_Output::resume() {
|
||||
|
||||
if (!playing) {
|
||||
[[maybe_unused]] int32_t hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
@@ -445,7 +442,7 @@ void FAudio_Output::reset() {
|
||||
|
||||
if (playing) {
|
||||
[[maybe_unused]] uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
}
|
||||
|
||||
FAudioSourceVoice_FlushSourceBuffers(sVoice);
|
||||
@@ -462,7 +459,7 @@ void FAudio_Output::setThrottle(unsigned short throttle_) {
|
||||
|
||||
[[maybe_unused]] uint32_t hr =
|
||||
FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_COMMIT_NOW);
|
||||
assert(hr == 0);
|
||||
VBAM_CHECK(hr == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -489,8 +486,11 @@ std::vector<AudioDevice> GetFAudioDevices() {
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> devices;
|
||||
#if defined(__WXMSW__)
|
||||
// Add a separate default device on Windows.
|
||||
devices.reserve(dev_count + 1);
|
||||
devices.push_back({_("Default device"), wxEmptyString});
|
||||
#endif
|
||||
|
||||
for (uint32_t i = 0; i < dev_count; i++) {
|
||||
FAudioDeviceDetails dd;
|
||||
@@ -499,9 +499,15 @@ std::vector<AudioDevice> GetFAudioDevices() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const wxString display_name(reinterpret_cast<wchar_t*>(dd.DisplayName));
|
||||
const wxString device_id(reinterpret_cast<wchar_t*>(dd.DeviceID));
|
||||
|
||||
// Convert to UTF-8.
|
||||
const std::vector<uint8_t> display_name_u8 =
|
||||
config::utf16_to_utf8(reinterpret_cast<uint16_t*>(dd.DisplayName));
|
||||
const std::vector<uint8_t> device_id_u8 =
|
||||
config::utf16_to_utf8(reinterpret_cast<uint16_t*>(dd.DeviceID));
|
||||
const wxString display_name = wxString::FromUTF8(
|
||||
reinterpret_cast<const char*>(display_name_u8.data()), display_name_u8.size());
|
||||
const wxString device_id = wxString::FromUTF8(
|
||||
reinterpret_cast<const char*>(device_id_u8.data()), device_id_u8.size());
|
||||
devices.push_back({display_name, device_id});
|
||||
}
|
||||
|
||||
|
@@ -30,13 +30,12 @@ typedef ALCboolean(ALC_APIENTRY* LPALCISEXTENSIONPRESENT)(ALCdevice* device,
|
||||
typedef const ALCchar*(ALC_APIENTRY* LPALCGETSTRING)(ALCdevice* device, ALCenum param);
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <wx/arrstr.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "core/gba/gbaGlobals.h"
|
||||
#include "core/gba/gbaSound.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
@@ -47,7 +46,7 @@ namespace internal {
|
||||
namespace {
|
||||
|
||||
// Debug
|
||||
#define ASSERT_SUCCESS assert(AL_NO_ERROR == alGetError())
|
||||
#define ASSERT_SUCCESS VBAM_CHECK(AL_NO_ERROR == alGetError())
|
||||
|
||||
#ifndef LOGALL
|
||||
// replace logging functions with comments
|
||||
@@ -171,7 +170,7 @@ void OpenAL::debugState() {
|
||||
|
||||
bool OpenAL::init(long sampleRate) {
|
||||
winlog("OpenAL::init\n");
|
||||
assert(initialized == false);
|
||||
VBAM_CHECK(initialized == false);
|
||||
|
||||
const wxString& audio_device = OPTION(kSoundAudioDevice);
|
||||
if (!audio_device.empty()) {
|
||||
@@ -191,9 +190,9 @@ bool OpenAL::init(long sampleRate) {
|
||||
}
|
||||
|
||||
context = alcCreateContext(device, nullptr);
|
||||
assert(context != nullptr);
|
||||
VBAM_CHECK(context != nullptr);
|
||||
ALCboolean retVal = alcMakeContextCurrent(context);
|
||||
assert(ALC_TRUE == retVal);
|
||||
VBAM_CHECK(ALC_TRUE == retVal);
|
||||
alGenBuffers(OPTION(kSoundBuffers), buffer);
|
||||
ASSERT_SUCCESS;
|
||||
alGenSources(1, &source);
|
||||
@@ -338,7 +337,7 @@ void OpenAL::write(uint16_t* finalWave, int length) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nBuffersProcessed > 0);
|
||||
VBAM_CHECK(nBuffersProcessed > 0);
|
||||
|
||||
// unqueue buffer
|
||||
tempBuffer = 0;
|
||||
|
@@ -1,10 +1,6 @@
|
||||
#ifndef WX_AUDIO_INTERNAL_XAUDIO2_H_
|
||||
#define WX_AUDIO_INTERNAL_XAUDIO2_H_
|
||||
|
||||
#if !defined(VBAM_ENABLE_FAUDIO)
|
||||
#error "This file should only be included if FAudio is enabled"
|
||||
#endif
|
||||
|
||||
#include "wx/audio/audio.h"
|
||||
|
||||
namespace audio {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#ifndef AUTOUPDATER_H
|
||||
#define AUTOUPDATER_H
|
||||
#ifndef VBAM_WX_AUTOUPDATER_AUTOUPDATER_H_
|
||||
#define VBAM_WX_AUTOUPDATER_AUTOUPDATER_H_
|
||||
|
||||
void initAutoupdater();
|
||||
void checkUpdatesUi();
|
||||
void shutdownAutoupdater();
|
||||
|
||||
#endif // AUTOUPDATER_H
|
||||
#endif // VBAM_WX_AUTOUPDATER_AUTOUPDATER_H_
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#include "../autoupdater.h"
|
||||
#include "sparkle-wrapper.h"
|
||||
#include "wx/autoupdater/autoupdater.h"
|
||||
|
||||
#include "wx/autoupdater/macos/sparkle-wrapper.h"
|
||||
|
||||
SparkleWrapper autoupdater;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#ifndef SPARKLE_WRAPPER_H
|
||||
#define SPARKLE_WRAPPER_H
|
||||
#ifndef VBAM_WX_AUTOUPDATER_MACOS_SPARKLE_WRAPPER_H_
|
||||
#define VBAM_WX_AUTOUPDATER_MACOS_SPARKLE_WRAPPER_H_
|
||||
|
||||
class SparkleWrapper
|
||||
{
|
||||
@@ -14,4 +14,4 @@ class SparkleWrapper
|
||||
Private* d;
|
||||
};
|
||||
|
||||
#endif // SPARKLE_WRAPPER_H
|
||||
#endif // VBAM_WX_AUTOUPDATER_MACOS_SPARKLE_WRAPPER_H_
|
||||
|
@@ -1,14 +1,13 @@
|
||||
#include "../autoupdater.h"
|
||||
#include "wx/autoupdater/autoupdater.h"
|
||||
|
||||
#include "core/base/version.h"
|
||||
#include "../../strutils.h"
|
||||
#include "winsparkle-wrapper.h"
|
||||
#include "wx/autoupdater/wxmsw/winsparkle-wrapper.h"
|
||||
|
||||
|
||||
void initAutoupdater()
|
||||
{
|
||||
// even if we are a nightly, only check latest stable version
|
||||
wxString version = strutils::split(kVbamVersion, '-')[0];
|
||||
const wxString version(kVbamMainVersion);
|
||||
#ifndef NO_HTTPS
|
||||
win_sparkle_set_appcast_url("https://data.visualboyadvance-m.org/appcast.xml");
|
||||
#else
|
||||
|
@@ -1,12 +1,13 @@
|
||||
#include "wx/autoupdater/wxmsw/winsparkle-wrapper.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <wx/file.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/msw/private.h>
|
||||
#include <wx/utils.h>
|
||||
#include "wx/wxvbam.h"
|
||||
#include "winsparkle-wrapper.h"
|
||||
#include "wx/msw/private.h"
|
||||
|
||||
#include "wx/autoupdater/wxmsw/winsparkle-rc.h"
|
||||
|
||||
WinSparkleDllWrapper *WinSparkleDllWrapper::GetInstance()
|
||||
{
|
||||
|
@@ -4,8 +4,6 @@
|
||||
#include <wx/string.h>
|
||||
#include <wx/dynlib.h>
|
||||
|
||||
#include "winsparkle-rc.h"
|
||||
|
||||
class WinSparkleDllWrapper {
|
||||
public:
|
||||
static WinSparkleDllWrapper *GetInstance();
|
||||
|
@@ -547,17 +547,17 @@ wxThread::ExitCode BackgroundInput::CheckKeyboard()
|
||||
// virtual key "i" is pressed
|
||||
if ((bits & 0x8000) && (previousState[i] & 0x8000) == 0) {
|
||||
if (handler && !wxWindow::FindFocus()) {
|
||||
wxKeyEvent ev(wxEVT_KEY_DOWN);
|
||||
ev.m_keyCode = xKeySym;
|
||||
handler->AddPendingEvent(ev);
|
||||
wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_DOWN);
|
||||
event->m_keyCode = xKeySym;
|
||||
handler->QueueEvent(event);
|
||||
}
|
||||
}
|
||||
// virtual key "i" is released
|
||||
else if (((bits & 0x8000) == 0) && (previousState[i] & 0x8000)) {
|
||||
if (handler && !wxWindow::FindFocus()) {
|
||||
wxKeyEvent ev(wxEVT_KEY_UP);
|
||||
ev.m_keyCode = xKeySym;
|
||||
handler->AddPendingEvent(ev);
|
||||
wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_UP);
|
||||
event->m_keyCode = xKeySym;
|
||||
handler->QueueEvent(event);
|
||||
}
|
||||
}
|
||||
previousState[i] = bits;
|
||||
|
@@ -11,6 +11,7 @@
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include "components/filters_interframe/interframe.h"
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/version.h"
|
||||
#include "core/gb/gb.h"
|
||||
#include "core/gb/gbCheats.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "core/gba/gbaGlobals.h"
|
||||
#include "core/gba/gbaPrint.h"
|
||||
#include "core/gba/gbaSound.h"
|
||||
#include "wx/config/cmdtab.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/option.h"
|
||||
#include "wx/dialogs/game-maker.h"
|
||||
@@ -29,14 +31,9 @@
|
||||
#define GetXRCDialog(n) \
|
||||
wxStaticCast(wxGetApp().frame->FindWindowByName(n), wxDialog)
|
||||
|
||||
bool cmditem_lt(const struct cmditem& cmd1, const struct cmditem& cmd2)
|
||||
{
|
||||
return wxStrcmp(cmd1.cmd, cmd2.cmd) < 0;
|
||||
}
|
||||
|
||||
void MainFrame::GetMenuOptionBool(const wxString& menuName, bool* field)
|
||||
{
|
||||
assert(field);
|
||||
VBAM_CHECK(field);
|
||||
*field = !*field;
|
||||
int id = wxXmlResource::GetXRCID(menuName);
|
||||
|
||||
@@ -52,7 +49,7 @@ void MainFrame::GetMenuOptionBool(const wxString& menuName, bool* field)
|
||||
void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
|
||||
const config::OptionID& option_id) {
|
||||
config::Option* option = config::Option::ByID(option_id);
|
||||
assert(option);
|
||||
VBAM_CHECK(option);
|
||||
|
||||
int id = wxXmlResource::GetXRCID(menu_name);
|
||||
for (size_t i = 0; i < checkable_mi.size(); i++) {
|
||||
@@ -68,7 +65,7 @@ void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
|
||||
option->SetInt(is_checked);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
VBAM_CHECK(false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -77,7 +74,7 @@ void MainFrame::GetMenuOptionConfig(const wxString& menu_name,
|
||||
|
||||
void MainFrame::GetMenuOptionInt(const wxString& menuName, int* field, int mask)
|
||||
{
|
||||
assert(field);
|
||||
VBAM_CHECK(field);
|
||||
int value = mask;
|
||||
bool is_checked = ((*field) & (mask)) != (value);
|
||||
int id = wxXmlResource::GetXRCID(menuName);
|
||||
@@ -365,7 +362,7 @@ EVT_HANDLER_MASK(SetLoadingDotCodeFile, "Load e-Reader Dot Code...", CMDEN_GBA)
|
||||
static wxString loaddotcodefile_path;
|
||||
wxFileDialog dlg(this, _("Select Dot Code file"), loaddotcodefile_path, wxEmptyString,
|
||||
_(
|
||||
"e-Reader Dot Code (*.bin;*.raw)|"
|
||||
"E-Reader Dot Code (*.bin;*.raw)|"
|
||||
"*.bin;*.raw"),
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
int ret = ShowModal(&dlg);
|
||||
@@ -387,7 +384,7 @@ EVT_HANDLER_MASK(SetSavingDotCodeFile, "Save e-Reader Dot Code...", CMDEN_GBA)
|
||||
static wxString savedotcodefile_path;
|
||||
wxFileDialog dlg(this, _("Select Dot Code file"), savedotcodefile_path, wxEmptyString,
|
||||
_(
|
||||
"e-Reader Dot Code (*.bin;*.raw)|"
|
||||
"E-Reader Dot Code (*.bin;*.raw)|"
|
||||
"*.bin;*.raw"),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
int ret = ShowModal(&dlg);
|
||||
@@ -1184,7 +1181,7 @@ EVT_HANDLER(AllowKeyboardBackgroundInput, "Allow keyboard background input (togg
|
||||
disableKeyboardBackgroundInput();
|
||||
if (OPTION(kUIAllowKeyboardBackgroundInput)) {
|
||||
if (panel && panel->panel) {
|
||||
enableKeyboardBackgroundInput(panel->panel->GetWindow());
|
||||
enableKeyboardBackgroundInput(panel->panel->GetWindow()->GetEventHandler());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2173,44 +2170,17 @@ EVT_HANDLER(EmulatorDirectories, "Directories...")
|
||||
|
||||
EVT_HANDLER(JoypadConfigure, "Joypad options...")
|
||||
{
|
||||
joy.PollAllJoysticks();
|
||||
|
||||
auto frame = wxGetApp().frame;
|
||||
bool joy_timer = frame->IsJoyPollTimerRunning();
|
||||
|
||||
if (!joy_timer) {
|
||||
frame->StartJoyPollTimer();
|
||||
}
|
||||
|
||||
if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) {
|
||||
update_joypad_opts();
|
||||
update_shortcut_opts();
|
||||
}
|
||||
|
||||
if (!joy_timer) {
|
||||
frame->StopJoyPollTimer();
|
||||
}
|
||||
|
||||
SetJoystick();
|
||||
}
|
||||
|
||||
EVT_HANDLER(Customize, "Customize UI...")
|
||||
{
|
||||
wxDialog* dlg = GetXRCDialog("AccelConfig");
|
||||
joy.PollAllJoysticks();
|
||||
|
||||
auto frame = wxGetApp().frame;
|
||||
bool joy_timer = frame->IsJoyPollTimerRunning();
|
||||
|
||||
if (!joy_timer) frame->StartJoyPollTimer();
|
||||
|
||||
if (ShowModal(dlg) == wxID_OK) {
|
||||
if (ShowModal(GetXRCDialog("AccelConfig")) == wxID_OK) {
|
||||
update_shortcut_opts();
|
||||
ResetMenuAccelerators();
|
||||
}
|
||||
|
||||
if (!joy_timer) frame->StopJoyPollTimer();
|
||||
|
||||
SetJoystick();
|
||||
}
|
||||
|
||||
#ifndef NO_ONLINEUPDATES
|
||||
|
@@ -0,0 +1,89 @@
|
||||
# This defines the vbam-wx-config target and the
|
||||
# `VBAM_LOCALIZABLE_WX_CONFIG_FILES` variable, containing the list of
|
||||
# localizable files in the vbam-wx-config target.
|
||||
|
||||
# I don't like duplicating/triplicating code, so I only declare
|
||||
# event handlers once, and copy them in other places they are needed
|
||||
# all using portable cmake code
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${VBAM_GENERATED_DIR}/wx/config/internal/cmdtab.cpp
|
||||
${VBAM_GENERATED_DIR}/wx/cmdhandlers.h
|
||||
${VBAM_GENERATED_DIR}/wx/cmd-evtable.h
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -D OUTDIR=${VBAM_GENERATED_DIR}/wx/ -P copy-events.cmake
|
||||
WORKING_DIRECTORY
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS
|
||||
../cmdevents.cpp
|
||||
copy-events.cmake
|
||||
)
|
||||
|
||||
add_library(vbam-wx-config OBJECT)
|
||||
|
||||
# Export the localizable files to the parent scope.
|
||||
set(VBAM_LOCALIZABLE_WX_CONFIG_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/bindings.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/command.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/cmdtab.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/emulated-gamepad.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/bindings-internal.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/bindings-internal.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/option-internal.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/option-internal.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/option-observer.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/option.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/strutils.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/user-input.cpp
|
||||
# Generated file.
|
||||
${VBAM_GENERATED_DIR}/wx/config/internal/cmdtab.cpp
|
||||
)
|
||||
|
||||
set(VBAM_LOCALIZABLE_WX_CONFIG_FILES
|
||||
${VBAM_LOCALIZABLE_WX_CONFIG_FILES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
target_sources(vbam-wx-config
|
||||
PRIVATE
|
||||
${VBAM_LOCALIZABLE_WX_CONFIG_FILES}
|
||||
|
||||
PUBLIC
|
||||
bindings.h
|
||||
command.h
|
||||
cmdtab.h
|
||||
emulated-gamepad.h
|
||||
option-id.h
|
||||
option-observer.h
|
||||
option-proxy.h
|
||||
option.h
|
||||
strutils.h
|
||||
user-input.h
|
||||
)
|
||||
|
||||
configure_wx_target(vbam-wx-config)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(vbam-wx-config-tests
|
||||
bindings-test.cpp
|
||||
command-test.cpp
|
||||
emulated-gamepad-test.cpp
|
||||
option-test.cpp
|
||||
strutils-test.cpp
|
||||
user-input-test.cpp
|
||||
)
|
||||
target_link_libraries(vbam-wx-config-tests
|
||||
# Test deps.
|
||||
vbam-core-fake
|
||||
vbam-wx-fake-opts
|
||||
|
||||
# Target deps.
|
||||
vbam-wx-config
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
configure_wx_target(vbam-wx-config-tests)
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
gtest_discover_tests(vbam-wx-config-tests)
|
||||
endif()
|
||||
endif()
|
||||
|
152
src/wx/config/bindings-test.cpp
Normal file
152
src/wx/config/bindings-test.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "wx/config/bindings.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include "wx/config/command.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
TEST(BindingsTest, Default) {
|
||||
const config::Bindings bindings;
|
||||
|
||||
// Check that the default bindings are set up correctly.
|
||||
auto inputs =
|
||||
bindings.InputsForCommand(config::GameCommand(config::GameJoy(0), config::GameKey::Up));
|
||||
EXPECT_TRUE(inputs.find(config::KeyboardInput('W')) != inputs.end());
|
||||
EXPECT_TRUE(inputs.find(config::JoyInput(config::JoyId(0), config::JoyControl::HatNorth, 0)) !=
|
||||
inputs.end());
|
||||
|
||||
inputs = bindings.InputsForCommand(config::ShortcutCommand(wxID_CLOSE));
|
||||
EXPECT_TRUE(inputs.find(config::KeyboardInput('W', wxMOD_CMD)) != inputs.end());
|
||||
|
||||
inputs = bindings.InputsForCommand(config::ShortcutCommand(XRCID("LoadGame01")));
|
||||
EXPECT_TRUE(inputs.find(config::KeyboardInput(WXK_F1)) != inputs.end());
|
||||
|
||||
// Check that the INI configuration for the keyboard is empty.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_TRUE(config.empty());
|
||||
}
|
||||
|
||||
// Tests that assigning a default input to another command generates the right
|
||||
// configuration.
|
||||
TEST(BindingsTest, AssignDefault) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Assign F1 to the "Close" command.
|
||||
bindings.AssignInputToCommand(config::KeyboardInput(WXK_F1),
|
||||
config::ShortcutCommand(wxID_CLOSE));
|
||||
|
||||
// The INI configuration should have NOOP set to F1, and Close set to F1.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_EQ(config.size(), 2);
|
||||
EXPECT_EQ(config[0].first, "Keyboard/NOOP");
|
||||
EXPECT_EQ(config[0].second, "F1");
|
||||
EXPECT_EQ(config[1].first, "Keyboard/CLOSE");
|
||||
EXPECT_EQ(config[1].second, "F1");
|
||||
}
|
||||
|
||||
// Tests that unassigning a default input generates the right configuration.
|
||||
TEST(BindingsTest, UnassignDefault) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Unassign F1.
|
||||
bindings.UnassignInput(config::KeyboardInput(WXK_F1));
|
||||
|
||||
// The INI configuration should have NOOP set to F1.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_EQ(config.size(), 1);
|
||||
EXPECT_EQ(config[0].first, "Keyboard/NOOP");
|
||||
EXPECT_EQ(config[0].second, "F1");
|
||||
}
|
||||
|
||||
// Tests that re-assigning a default input to its default command generates the
|
||||
// right configuration.
|
||||
TEST(BindingsTest, ReassignDefault) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Assign F1 to the "Close" command.
|
||||
bindings.AssignInputToCommand(config::KeyboardInput(WXK_F1),
|
||||
config::ShortcutCommand(wxID_CLOSE));
|
||||
|
||||
// Re-assign F1 to the "LoadGame01" command.
|
||||
bindings.AssignInputToCommand(config::KeyboardInput(WXK_F1),
|
||||
config::ShortcutCommand(XRCID("LoadGame01")));
|
||||
|
||||
// The INI configuration should be empty.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_TRUE(config.empty());
|
||||
}
|
||||
|
||||
// Tests that assigning an input to "NOOP" properly disables the default input.
|
||||
TEST(BindingsTest, AssignToNoop) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Assign F1 to the "NOOP" command.
|
||||
bindings.AssignInputToCommand(config::KeyboardInput(WXK_F1),
|
||||
config::ShortcutCommand(XRCID("NOOP")));
|
||||
|
||||
const auto command = bindings.CommandForInput(config::KeyboardInput(WXK_F1));
|
||||
EXPECT_FALSE(command.has_value());
|
||||
|
||||
// The INI configuration should have NOOP set to F1 and nothing more.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_EQ(config.size(), 1);
|
||||
EXPECT_EQ(config[0].first, "Keyboard/NOOP");
|
||||
EXPECT_EQ(config[0].second, "F1");
|
||||
}
|
||||
|
||||
// Tests that assigning an input not used as a default shortcut to "NOOP" does
|
||||
// nothing.
|
||||
TEST(BindingsTest, AssignUnusedToNoop) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Assign "T" to the "NOOP" command.
|
||||
bindings.AssignInputToCommand(config::KeyboardInput('T'), config::ShortcutCommand(XRCID("NOOP")));
|
||||
|
||||
// The INI configuration should be empty.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_TRUE(config.empty());
|
||||
|
||||
// "T" should have no assignment.
|
||||
const auto command = bindings.CommandForInput(config::KeyboardInput('T'));
|
||||
EXPECT_FALSE(command.has_value());
|
||||
}
|
||||
|
||||
// Tests that assigning a default input to a Game command works as expected.
|
||||
TEST(BindingsTest, AssignDefaultToGame) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Assign F1 to the "Up" command and clear all of the default input for the
|
||||
// "Up" command.
|
||||
bindings.AssignInputsToCommand({config::KeyboardInput(WXK_F1)},
|
||||
config::GameCommand(config::GameJoy(0), config::GameKey::Up));
|
||||
|
||||
// The INI configuration should have NOOP set to F1.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_EQ(config.size(), 1);
|
||||
EXPECT_EQ(config[0].first, "Keyboard/NOOP");
|
||||
EXPECT_EQ(config[0].second, "F1");
|
||||
|
||||
EXPECT_EQ(
|
||||
bindings.InputsForCommand(config::GameCommand(config::GameJoy(0), config::GameKey::Up)),
|
||||
std::unordered_set<config::UserInput>{config::KeyboardInput(WXK_F1)});
|
||||
|
||||
EXPECT_EQ(bindings.CommandForInput(config::KeyboardInput(WXK_F1)),
|
||||
config::Command(config::GameCommand(config::GameJoy(0), config::GameKey::Up)));
|
||||
}
|
||||
|
||||
// Tests the "ClearCommandAssignments" method.
|
||||
TEST(BindingsTest, ClearCommand) {
|
||||
config::Bindings bindings;
|
||||
|
||||
// Clear "CLOSE" assignments.
|
||||
bindings.ClearCommandAssignments(config::ShortcutCommand(wxID_CLOSE));
|
||||
|
||||
// The INI configuration should only have the NOOP assignment.
|
||||
const auto config = bindings.GetKeyboardConfiguration();
|
||||
EXPECT_EQ(config.size(), 1);
|
||||
EXPECT_EQ(config[0].first, "Keyboard/NOOP");
|
||||
EXPECT_EQ(config[0].second, "CTRL+W");
|
||||
|
||||
EXPECT_TRUE(bindings.InputsForCommand(config::ShortcutCommand(wxID_CLOSE)).empty());
|
||||
}
|
252
src/wx/config/bindings.cpp
Normal file
252
src/wx/config/bindings.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "wx/config/bindings.h"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
#define VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
#include "wx/config/internal/bindings-internal.h"
|
||||
#undef VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
int NoopCommand() {
|
||||
static const int noop = XRCID("NOOP");
|
||||
return noop;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
const std::unordered_set<UserInput>& Bindings::DefaultInputsForCommand(const Command& command) {
|
||||
return internal::DefaultInputsForCommand(command);
|
||||
}
|
||||
|
||||
Bindings::Bindings() {
|
||||
// Set up default shortcuts.
|
||||
for (const auto& iter : internal::DefaultInputs()) {
|
||||
for (const auto& input : iter.second) {
|
||||
AssignInputToCommand(input, iter.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bindings::Bindings(
|
||||
const std::unordered_map<Command, std::unordered_set<UserInput>>& control_to_inputs,
|
||||
const std::unordered_map<UserInput, Command>& input_to_control,
|
||||
const std::unordered_map<UserInput, ShortcutCommand>& disabled_defaults)
|
||||
: control_to_inputs_(control_to_inputs.begin(), control_to_inputs.end()),
|
||||
input_to_control_(input_to_control.begin(), input_to_control.end()),
|
||||
disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {}
|
||||
|
||||
std::vector<std::pair<wxString, wxString>> Bindings::GetKeyboardConfiguration() const {
|
||||
std::vector<std::pair<wxString, wxString>> config;
|
||||
config.reserve(control_to_inputs_.size() + 1);
|
||||
|
||||
if (!disabled_defaults_.empty()) {
|
||||
std::unordered_set<UserInput> noop_inputs;
|
||||
for (const auto& iter : disabled_defaults_) {
|
||||
noop_inputs.insert(iter.first);
|
||||
}
|
||||
config.push_back(std::make_pair(ShortcutCommand(NoopCommand()).ToConfigString(),
|
||||
UserInput::SpanToConfigString(noop_inputs)));
|
||||
}
|
||||
|
||||
for (const auto& iter : control_to_inputs_) {
|
||||
if (iter.first.is_game()) {
|
||||
// We only consider shortcut assignments here.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gather the inputs for this command.
|
||||
std::unordered_set<UserInput> inputs;
|
||||
for (const auto& input : iter.second) {
|
||||
if (internal::IsDefaultInputForCommand(iter.first, input)) {
|
||||
// Default assignments are ignored.
|
||||
continue;
|
||||
}
|
||||
// Not a default input.
|
||||
inputs.insert(input);
|
||||
}
|
||||
|
||||
if (!inputs.empty()) {
|
||||
config.push_back(std::make_pair(iter.first.shortcut().ToConfigString(),
|
||||
UserInput::SpanToConfigString(inputs)));
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<std::pair<GameCommand, wxString>> Bindings::GetJoypadConfiguration() const {
|
||||
std::vector<std::pair<GameCommand, wxString>> config;
|
||||
config.reserve(kNbGameKeys * kNbJoypads);
|
||||
|
||||
for (const auto& game_command : internal::kOrderedGameCommands) {
|
||||
const auto iter = control_to_inputs_.find(Command(game_command));
|
||||
if (iter == control_to_inputs_.end()) {
|
||||
config.push_back(std::make_pair(game_command, wxEmptyString));
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::unordered_set<UserInput>& inputs = iter->second;
|
||||
config.push_back(std::make_pair(game_command, UserInput::SpanToConfigString(inputs)));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::unordered_set<UserInput> Bindings::InputsForCommand(const Command& command) const {
|
||||
if (command.is_shortcut() && command.shortcut().id() == NoopCommand()) {
|
||||
std::unordered_set<UserInput> noop_inputs;
|
||||
for (const auto& iter : disabled_defaults_) {
|
||||
noop_inputs.insert(iter.first);
|
||||
}
|
||||
return noop_inputs;
|
||||
}
|
||||
|
||||
auto iter = control_to_inputs_.find(command);
|
||||
if (iter == control_to_inputs_.end()) {
|
||||
return {};
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
nonstd::optional<Command> Bindings::CommandForInput(const UserInput& input) const {
|
||||
const auto iter = input_to_control_.find(input);
|
||||
if (iter == input_to_control_.end()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
Bindings Bindings::Clone() const {
|
||||
return Bindings(this->control_to_inputs_, this->input_to_control_, this->disabled_defaults_);
|
||||
}
|
||||
|
||||
void Bindings::AssignInputToCommand(const UserInput& input, const Command& command) {
|
||||
if (command.is_shortcut() && command.shortcut().id() == NoopCommand()) {
|
||||
// "Assigning to Noop" means unassinging the default binding.
|
||||
UnassignDefaultBinding(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the existing binding if it exists.
|
||||
auto iter = input_to_control_.find(input);
|
||||
if (iter != input_to_control_.end()) {
|
||||
UnassignInput(input);
|
||||
}
|
||||
|
||||
if (command.is_shortcut()) {
|
||||
const ShortcutCommand& shortcut_command = command.shortcut();
|
||||
auto disabled_iter = disabled_defaults_.find(input);
|
||||
if (disabled_iter != disabled_defaults_.end()) {
|
||||
const ShortcutCommand& original_command = disabled_iter->second;
|
||||
if (original_command == shortcut_command) {
|
||||
// Restoring a disabled input. Remove from the disabled set.
|
||||
disabled_defaults_.erase(disabled_iter);
|
||||
}
|
||||
// Then, just continue normally.
|
||||
}
|
||||
}
|
||||
|
||||
control_to_inputs_[command].emplace(input);
|
||||
input_to_control_.emplace(std::make_pair(input, command));
|
||||
}
|
||||
|
||||
void Bindings::AssignInputsToCommand(const std::unordered_set<UserInput>& inputs,
|
||||
const Command& command) {
|
||||
// Remove the existing binding if it exists.
|
||||
const auto iter = control_to_inputs_.find(command);
|
||||
if (iter != control_to_inputs_.end()) {
|
||||
// We need to make a copy here because the iterator is going to be invalidated.
|
||||
const std::unordered_set<UserInput> inputs_to_unassign = iter->second;
|
||||
for (const UserInput& user_input : inputs_to_unassign) {
|
||||
UnassignInput(user_input);
|
||||
}
|
||||
}
|
||||
|
||||
for (const UserInput& user_input : inputs) {
|
||||
AssignInputToCommand(user_input, command);
|
||||
}
|
||||
}
|
||||
|
||||
void Bindings::UnassignInput(const UserInput& input) {
|
||||
VBAM_CHECK(input);
|
||||
|
||||
auto iter = input_to_control_.find(input);
|
||||
if (iter == input_to_control_.end()) {
|
||||
// Input not found, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (iter->second.is_shortcut()) {
|
||||
if (internal::IsDefaultInputForCommand(iter->second, input)) {
|
||||
// Unassigning a default binding has some special handling.
|
||||
UnassignDefaultBinding(input);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, just remove it from the 2 maps.
|
||||
auto command_iter = control_to_inputs_.find(iter->second);
|
||||
VBAM_CHECK(command_iter != control_to_inputs_.end());
|
||||
|
||||
command_iter->second.erase(input);
|
||||
if (command_iter->second.empty()) {
|
||||
// Remove empty set.
|
||||
control_to_inputs_.erase(command_iter);
|
||||
}
|
||||
input_to_control_.erase(iter);
|
||||
}
|
||||
|
||||
void Bindings::ClearCommandAssignments(const Command& command) {
|
||||
const auto iter = control_to_inputs_.find(command);
|
||||
if (iter == control_to_inputs_.end()) {
|
||||
// Command not found, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep a copy of the inputs to unassign.
|
||||
std::unordered_set<UserInput> inputs_to_unassign(iter->second);
|
||||
|
||||
// Unassign all inputs.
|
||||
for (const UserInput& input : inputs_to_unassign) {
|
||||
UnassignInput(input);
|
||||
}
|
||||
}
|
||||
|
||||
void Bindings::UnassignDefaultBinding(const UserInput& input) {
|
||||
auto input_iter = input_to_control_.find(input);
|
||||
if (input_iter == input_to_control_.end()) {
|
||||
// This can happen if the INI file provided by the user has an invalid
|
||||
// option. In this case, just silently ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input_iter->second.is_shortcut()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!internal::IsDefaultInputForCommand(input_iter->second, input)) {
|
||||
// As above, we have already removed the default binding, ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
auto command_iter = control_to_inputs_.find(input_iter->second);
|
||||
VBAM_CHECK(command_iter != control_to_inputs_.end());
|
||||
|
||||
command_iter->second.erase(input);
|
||||
if (command_iter->second.empty()) {
|
||||
control_to_inputs_.erase(command_iter);
|
||||
}
|
||||
|
||||
disabled_defaults_.emplace(std::make_pair(input, input_iter->second.shortcut()));
|
||||
input_to_control_.erase(input_iter);
|
||||
}
|
||||
|
||||
} // namespace config
|
104
src/wx/config/bindings.h
Normal file
104
src/wx/config/bindings.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef VBAM_WX_CONFIG_BINDINGS_H_
|
||||
#define VBAM_WX_CONFIG_BINDINGS_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "wx/config/command.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
// wxWidgets only goes up to `wxID_FILE9` but we want 10 recent files.
|
||||
#define wxID_FILE10 (wxID_FILE9 + 1)
|
||||
|
||||
namespace config {
|
||||
|
||||
// Bindings is a class that manages the association between commands and
|
||||
// user inputs. It is used to manage the shortcuts configuration. The class
|
||||
// provides methods to assign and unassign inputs to commands, as well as
|
||||
// retrieve the current configuration for the INI file.
|
||||
class Bindings {
|
||||
public:
|
||||
// Returns the list of default inputs for `command`.
|
||||
static const std::unordered_set<UserInput>& DefaultInputsForCommand(const Command& command);
|
||||
|
||||
Bindings();
|
||||
~Bindings() = default;
|
||||
|
||||
Bindings(Bindings&&) noexcept = default;
|
||||
Bindings& operator=(Bindings&&) noexcept = default;
|
||||
|
||||
// Disable copy and copy assignment operator.
|
||||
// `Clone()` is provided only for the configuration window, this class
|
||||
// should otherwise be treated as move-only. If you wish to access the
|
||||
// Bindings configuration, do it from `wxGetApp().bindings()`.
|
||||
Bindings(const Bindings&) = delete;
|
||||
Bindings& operator=(const Bindings&) = delete;
|
||||
|
||||
// Returns the shortcuts configuration for the INI file.
|
||||
// Internally, there are global default system inputs that are immediately
|
||||
// available on first run. For the configuration saved in the [Keyboard]
|
||||
// section of the vbam.ini file, we only keep track of the following:
|
||||
// - Disabled default input. These appear under [Keyboard/NOOP].
|
||||
// - User-added custom bindings. These appear under [Keyboard/CommandName].
|
||||
// Essentially, this is a diff between the default shortcuts and the user
|
||||
// configuration.
|
||||
std::vector<std::pair<wxString, wxString>> GetKeyboardConfiguration() const;
|
||||
|
||||
// Returns the game control configuration for the INI file. These go in the
|
||||
// [Joypad] section of the INI file.
|
||||
std::vector<std::pair<GameCommand, wxString>> GetJoypadConfiguration() const;
|
||||
|
||||
// Returns the list of input currently configured for `command`.
|
||||
std::unordered_set<UserInput> InputsForCommand(const Command& command) const;
|
||||
|
||||
// Returns the Command currently assigned to `input` or nullopt if none.
|
||||
nonstd::optional<Command> CommandForInput(const UserInput& input) const;
|
||||
|
||||
// Returns a copy of this object. This can be an expensive operation and
|
||||
// should only be used to modify the currently active shortcuts
|
||||
// configuration.
|
||||
Bindings Clone() const;
|
||||
|
||||
// Assigns `input` to `command`. Silently unassigns `input` if it is already
|
||||
// assigned to another command.
|
||||
void AssignInputToCommand(const UserInput& input, const Command& command);
|
||||
|
||||
// Assigns `inputs` to `command`. Silently unassigns any of `inputs` if they
|
||||
// are already assigned to another command. Any input previously assigned to
|
||||
// `command` will be cleared.
|
||||
void AssignInputsToCommand(const std::unordered_set<UserInput>& inputs, const Command& command);
|
||||
|
||||
// Removes `input` assignment. No-op if `input` is not assigned. `input`
|
||||
// must be a valid UserInput. Call will assert otherwise. Call will assert otherwise.
|
||||
void UnassignInput(const UserInput& input);
|
||||
|
||||
// Removes all assignments for `command`. No-op if `command` has no assignment.
|
||||
void ClearCommandAssignments(const Command& command);
|
||||
|
||||
private:
|
||||
// Faster constructor for explicit copy.
|
||||
Bindings(
|
||||
const std::unordered_map<Command, std::unordered_set<UserInput>>& control_to_inputs,
|
||||
const std::unordered_map<UserInput, Command>& input_to_control,
|
||||
const std::unordered_map<UserInput, ShortcutCommand>& disabled_defaults);
|
||||
|
||||
// Helper method to unassign a binding used by the default configuration.
|
||||
// This requires special handling since the INI configuration is a diff
|
||||
// between the default bindings and the user configuration.
|
||||
void UnassignDefaultBinding(const UserInput& input);
|
||||
|
||||
// Map of command to their associated input set.
|
||||
std::unordered_map<Command, std::unordered_set<UserInput>> control_to_inputs_;
|
||||
// Reverse map of the above. An input can only map to a single command.
|
||||
std::unordered_map<UserInput, Command> input_to_control_;
|
||||
// Disabled default shortcuts. This is used to easily retrieve the
|
||||
// configuration to save in the INI file.
|
||||
std::unordered_map<UserInput, ShortcutCommand> disabled_defaults_;
|
||||
};
|
||||
|
||||
using BindingsProvider = std::function<Bindings*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_BINDINGS_H_
|
55
src/wx/config/cmdtab.cpp
Normal file
55
src/wx/config/cmdtab.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "wx/config/cmdtab.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wx/wxcrt.h>
|
||||
|
||||
#include "core/base/check.h"
|
||||
|
||||
// Initializer for struct cmditem
|
||||
cmditem new_cmditem(const wxString cmd,
|
||||
const wxString name,
|
||||
int cmd_id,
|
||||
int mask_flags,
|
||||
wxMenuItem* mi) {
|
||||
return cmditem {cmd, name, cmd_id, mask_flags, mi};
|
||||
}
|
||||
|
||||
namespace config {
|
||||
wxString GetCommandINIEntry(int command) {
|
||||
for (const auto& cmd_item : cmdtab) {
|
||||
if (cmd_item.cmd_id == command) {
|
||||
return wxString::Format("Keyboard/%s", cmd_item.cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Command not found. This should never happen.
|
||||
VBAM_NOTREACHED();
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString GetCommandHelper(int command) {
|
||||
for (const auto& cmd_item : cmdtab) {
|
||||
if (cmd_item.cmd_id == command) {
|
||||
return cmd_item.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Command not found. This should never happen.
|
||||
VBAM_NOTREACHED();
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
nonstd::optional<int> CommandFromConfigString(const wxString& config) {
|
||||
const cmditem dummy = new_cmditem(config);
|
||||
const auto iter = std::lower_bound(cmdtab.begin(), cmdtab.end(), dummy, [](const cmditem& cmd1, const cmditem& cmd2) {
|
||||
return wxStrcmp(cmd1.cmd, cmd2.cmd) < 0;
|
||||
});
|
||||
|
||||
if (iter == cmdtab.end() || iter->cmd != config) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return iter->cmd_id;
|
||||
}
|
||||
} // namespace config
|
85
src/wx/config/cmdtab.h
Normal file
85
src/wx/config/cmdtab.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#ifndef VBAM_WX_CONFIG_CMDTAB_H_
|
||||
#define VBAM_WX_CONFIG_CMDTAB_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <optional.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
// Forward declaration.
|
||||
class wxMenuItem;
|
||||
|
||||
// List of all commands with their descriptions
|
||||
// sorted by cmd field for binary searching
|
||||
// filled in by copy-events.cmake
|
||||
struct cmditem {
|
||||
const wxString cmd;
|
||||
const wxString name;
|
||||
int cmd_id;
|
||||
int mask_flags; // if non-0, one of the flags must be turned on in win
|
||||
// to enable this command
|
||||
wxMenuItem* mi; // the menu item to invoke command, if present
|
||||
};
|
||||
|
||||
extern std::vector<cmditem> cmdtab;
|
||||
|
||||
// Initializer for struct cmditem
|
||||
cmditem new_cmditem(const wxString cmd = "",
|
||||
const wxString name = "",
|
||||
int cmd_id = 0,
|
||||
int mask_flags = 0,
|
||||
wxMenuItem* mi = nullptr);
|
||||
|
||||
namespace config {
|
||||
// Returns the command INI entry name for the given XRC ID. Will assert if
|
||||
// the command is not found. The INI entry name is the command name prefixed
|
||||
// with "Keyboard/". This is used to store the command in the INI file.
|
||||
// Examples:
|
||||
// * wxID_OPEN -> "Keyboard/OPEN"
|
||||
// * XRCID("NOOP") -> "Keyboard/NOOP"
|
||||
// O(n) search.
|
||||
wxString GetCommandINIEntry(int xrc_id);
|
||||
|
||||
// Returns the command helper string for the given XRC ID. Will assert if
|
||||
// the command is not found.
|
||||
// O(n) search.
|
||||
wxString GetCommandHelper(int xrc_id);
|
||||
|
||||
// Returns the XRC ID for the given command config name, without the
|
||||
// "Keyboard/" prefix. Examples:
|
||||
// * "OPEN" -> wxID_OPEN
|
||||
// * "Keyboard/OPEN" -> nonstd::nullopt
|
||||
// * "NOOP" -> XRCID("NOOP")
|
||||
// O(log(n)) search.
|
||||
nonstd::optional<int> CommandFromConfigString(const wxString& config);
|
||||
}
|
||||
|
||||
// here are those conditions
|
||||
enum { CMDEN_GB = (1 << 0), // GB ROM loaded
|
||||
CMDEN_GBA = (1 << 1), // GBA ROM loaded
|
||||
// the rest imply the above, unless:
|
||||
// _ANY -> does not imply either
|
||||
// _GBA -> only implies GBA
|
||||
CMDEN_REWIND = (1 << 2), // rewind states available
|
||||
CMDEN_SREC = (1 << 3), // sound recording in progress
|
||||
CMDEN_NSREC = (1 << 4), // no sound recording
|
||||
CMDEN_VREC = (1 << 5), // video recording
|
||||
CMDEN_NVREC = (1 << 6), // no video recording
|
||||
CMDEN_GREC = (1 << 7), // game recording
|
||||
CMDEN_NGREC = (1 << 8), // no game recording
|
||||
CMDEN_GPLAY = (1 << 9), // game playback
|
||||
CMDEN_NGPLAY = (1 << 10), // no game playback
|
||||
CMDEN_SAVST = (1 << 11), // any save states
|
||||
CMDEN_GDB = (1 << 12), // gdb connected
|
||||
CMDEN_NGDB_GBA = (1 << 13), // gdb not connected
|
||||
CMDEN_NGDB_ANY = (1 << 14), // gdb not connected
|
||||
CMDEN_NREC_ANY = (1 << 15), // not a/v recording
|
||||
CMDEN_LINK_ANY = (1 << 16), // link enabled
|
||||
|
||||
CMDEN_NEVER = (1 << 31) // never (for NOOP)
|
||||
};
|
||||
#define ONLOAD_CMDEN (CMDEN_NSREC | CMDEN_NVREC | CMDEN_NGREC | CMDEN_NGPLAY)
|
||||
#define UNLOAD_CMDEN_KEEP (CMDEN_NGDB_ANY | CMDEN_NREC_ANY | CMDEN_LINK_ANY)
|
||||
|
||||
#endif // VBAM_WX_CONFIG_CMDTAB_H_
|
51
src/wx/config/command-test.cpp
Normal file
51
src/wx/config/command-test.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "wx/config/command.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
TEST(GameCommandTest, Basic) {
|
||||
config::GameCommand command(config::GameJoy(0), config::GameKey::Up);
|
||||
|
||||
EXPECT_EQ(command.joypad(), config::GameJoy(0));
|
||||
EXPECT_EQ(command.game_key(), config::GameKey::Up);
|
||||
EXPECT_EQ(command.ToConfigString(), "Joypad/1/Up");
|
||||
}
|
||||
|
||||
TEST(ShortcutCommandTest, Basic) {
|
||||
config::ShortcutCommand command(wxID_OPEN);
|
||||
|
||||
EXPECT_EQ(command.id(), wxID_OPEN);
|
||||
EXPECT_EQ(command.ToConfigString(), "Keyboard/OPEN");
|
||||
}
|
||||
|
||||
TEST(CommandTest, FromString) {
|
||||
const auto game_command = config::Command::FromString("Joypad/1/Up");
|
||||
ASSERT_TRUE(game_command.has_value());
|
||||
ASSERT_TRUE(game_command->is_game());
|
||||
|
||||
const config::GameCommand& game = game_command->game();
|
||||
EXPECT_EQ(game.joypad(), config::GameJoy(0));
|
||||
EXPECT_EQ(game.game_key(), config::GameKey::Up);
|
||||
|
||||
const auto shortcut_command = config::Command::FromString("Keyboard/OPEN");
|
||||
ASSERT_TRUE(shortcut_command.has_value());
|
||||
ASSERT_TRUE(shortcut_command->is_shortcut());
|
||||
|
||||
const config::ShortcutCommand& shortcut = shortcut_command->shortcut();
|
||||
EXPECT_EQ(shortcut.id(), wxID_OPEN);
|
||||
}
|
||||
|
||||
TEST(CommandTest, FromStringInvalid) {
|
||||
// Need to disable logging to test for errors.
|
||||
const wxLogNull disable_logging;
|
||||
|
||||
const auto game_command = config::Command::FromString("Joypad/1/Invalid");
|
||||
EXPECT_FALSE(game_command.has_value());
|
||||
|
||||
const auto shortcut_command = config::Command::FromString("Keyboard/INVALID");
|
||||
EXPECT_FALSE(shortcut_command.has_value());
|
||||
|
||||
const auto invalid_command = config::Command::FromString("INVALID");
|
||||
EXPECT_FALSE(invalid_command.has_value());
|
||||
}
|
162
src/wx/config/command.cpp
Normal file
162
src/wx/config/command.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "wx/config/command.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <wx/translation.h>
|
||||
|
||||
#include "wx/config/cmdtab.h"
|
||||
#include "wx/config/strutils.h"
|
||||
|
||||
namespace config {
|
||||
namespace {
|
||||
|
||||
constexpr int GameKeyToInt(const GameKey& game_key) {
|
||||
return static_cast<int>(game_key);
|
||||
}
|
||||
|
||||
// Returns true if `joypad` is in a valid joypad range.
|
||||
constexpr bool JoypadInRange(const int& joypad) {
|
||||
constexpr size_t kMinJoypadIndex = 0;
|
||||
return static_cast<size_t>(joypad) >= kMinJoypadIndex &&
|
||||
static_cast<size_t>(joypad) < kNbJoypads;
|
||||
}
|
||||
|
||||
wxString GameKeyToUxString(const GameKey& game_key) {
|
||||
// Note: this must match GUI widget names or GUI won't work
|
||||
// This array's order determines tab order as well
|
||||
static const std::array<wxString, kNbGameKeys> kGameKeyStrings = {
|
||||
_("Up"), _("Down"), _("Left"), _("Right"), _("A"),
|
||||
_("B"), _("L"), _("R"), _("Select"), _("Start"),
|
||||
_("Motion Up"), _("Motion Down"), _("Motion Left"), _("Motion Right"), _("Motion In"),
|
||||
_("Motion Out"), _("Auto A"), _("Auto B"), _("Speed"), _("Capture"),
|
||||
_("GameShark"),
|
||||
};
|
||||
return kGameKeyStrings[GameKeyToInt(game_key)];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
wxString GameKeyToString(const GameKey& game_key) {
|
||||
// Note: this must match GUI widget names or GUI won't work
|
||||
// This array's order determines tab order as well
|
||||
static const std::array<wxString, kNbGameKeys> kGameKeyStrings = {
|
||||
"Up",
|
||||
"Down",
|
||||
"Left",
|
||||
"Right",
|
||||
"A",
|
||||
"B",
|
||||
"L",
|
||||
"R",
|
||||
"Select",
|
||||
"Start",
|
||||
"MotionUp",
|
||||
"MotionDown",
|
||||
"MotionLeft",
|
||||
"MotionRight",
|
||||
"MotionIn",
|
||||
"MotionOut",
|
||||
"AutoA",
|
||||
"AutoB",
|
||||
"Speed",
|
||||
"Capture",
|
||||
"GS",
|
||||
};
|
||||
return kGameKeyStrings[GameKeyToInt(game_key)];
|
||||
}
|
||||
|
||||
nonstd::optional<GameKey> StringToGameKey(const wxString& input) {
|
||||
static const std::map<wxString, GameKey> kStringToGameKey = {
|
||||
{ "Up", GameKey::Up },
|
||||
{ "Down", GameKey::Down },
|
||||
{ "Left", GameKey::Left },
|
||||
{ "Right", GameKey::Right },
|
||||
{ "A", GameKey::A },
|
||||
{ "B", GameKey::B },
|
||||
{ "L", GameKey::L },
|
||||
{ "R", GameKey::R },
|
||||
{ "Select", GameKey::Select },
|
||||
{ "Start", GameKey::Start },
|
||||
{ "MotionUp", GameKey::MotionUp },
|
||||
{ "MotionDown", GameKey::MotionDown },
|
||||
{ "MotionLeft", GameKey::MotionLeft },
|
||||
{ "MotionRight", GameKey::MotionRight },
|
||||
{ "MotionIn", GameKey::MotionIn },
|
||||
{ "MotionOut", GameKey::MotionOut },
|
||||
{ "AutoA", GameKey::AutoA },
|
||||
{ "AutoB", GameKey::AutoB },
|
||||
{ "Speed", GameKey::Speed },
|
||||
{ "Capture", GameKey::Capture },
|
||||
{ "GS", GameKey::Gameshark },
|
||||
};
|
||||
|
||||
const auto iter = kStringToGameKey.find(input);
|
||||
if (iter == kStringToGameKey.end()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
wxString GameCommand::ToConfigString() const {
|
||||
return wxString::Format("Joypad/%zu/%s", joypad_.ux_index(), GameKeyToString(game_key_));
|
||||
}
|
||||
wxString GameCommand::ToUXString() const {
|
||||
return wxString::Format(_("Joypad %zu %s"), joypad_.ux_index(), GameKeyToUxString(game_key()));
|
||||
}
|
||||
|
||||
wxString ShortcutCommand::ToConfigString() const {
|
||||
return GetCommandINIEntry(id_);
|
||||
}
|
||||
|
||||
// static
|
||||
nonstd::optional<Command> Command::FromString(const wxString& name) {
|
||||
static const wxString kKeyboard("Keyboard");
|
||||
static const wxString kJoypad("Joypad");
|
||||
|
||||
bool is_keyboard = !wxStrncmp(name, kKeyboard, kKeyboard.size());
|
||||
bool is_joypad = !wxStrncmp(name, kJoypad, kJoypad.size());
|
||||
if (!is_keyboard && !is_joypad) {
|
||||
wxLogDebug("Doesn't start with joypad or keyboard");
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto parts = config::str_split(name, "/");
|
||||
if (is_joypad) {
|
||||
if (parts.size() != 3) {
|
||||
wxLogDebug("Wrong split size: %d", parts.size());
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
const int joypad = parts[1][0] - '1';
|
||||
if (!JoypadInRange(joypad)) {
|
||||
wxLogDebug("Wrong joypad index: %d", joypad);
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
const nonstd::optional<GameKey> game_key = StringToGameKey(parts[2]);
|
||||
if (!game_key) {
|
||||
wxLogDebug("Failed to parse game_key: %s", parts[2]);
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return Command(GameCommand(GameJoy(joypad), *game_key));
|
||||
} else {
|
||||
if (parts.size() != 2) {
|
||||
wxLogDebug("Wrong split size: %d", parts.size());
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
const auto xrc_id = CommandFromConfigString(parts[1]);
|
||||
if (!xrc_id.has_value()) {
|
||||
wxLogDebug("Command ID %s not found", parts[1]);
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return Command(ShortcutCommand(xrc_id.value()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace config
|
268
src/wx/config/command.h
Normal file
268
src/wx/config/command.h
Normal file
@@ -0,0 +1,268 @@
|
||||
#ifndef VBAM_WX_CONFIG_COMMAND_H_
|
||||
#define VBAM_WX_CONFIG_COMMAND_H_
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <optional.hpp>
|
||||
#include <variant.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "core/base/check.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
// clang-format off
|
||||
// Represents an in-game input.
|
||||
enum class GameKey {
|
||||
Up = 0,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
A,
|
||||
B,
|
||||
L,
|
||||
R,
|
||||
Select,
|
||||
Start,
|
||||
MotionUp,
|
||||
MotionDown,
|
||||
MotionLeft,
|
||||
MotionRight,
|
||||
MotionIn,
|
||||
MotionOut,
|
||||
AutoA,
|
||||
AutoB,
|
||||
Speed,
|
||||
Capture,
|
||||
Gameshark,
|
||||
Last = Gameshark
|
||||
};
|
||||
static constexpr size_t kNbGameKeys = static_cast<size_t>(GameKey::Last) + 1;
|
||||
|
||||
static constexpr std::array<GameKey, config::kNbGameKeys> kAllGameKeys = {
|
||||
GameKey::Up, GameKey::Down, GameKey::Left,
|
||||
GameKey::Right, GameKey::A, GameKey::B,
|
||||
GameKey::L, GameKey::R, GameKey::Select,
|
||||
GameKey::Start, GameKey::MotionUp, GameKey::MotionDown,
|
||||
GameKey::MotionLeft, GameKey::MotionRight, GameKey::MotionIn,
|
||||
GameKey::MotionOut, GameKey::AutoA, GameKey::AutoB,
|
||||
GameKey::Speed, GameKey::Capture, GameKey::Gameshark,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static constexpr size_t kNbJoypads = 4;
|
||||
|
||||
// Represents an emulated joypad. The internal index is zero-based.
|
||||
class GameJoy {
|
||||
public:
|
||||
constexpr explicit GameJoy(size_t index) : index_(index) { VBAM_CHECK(index < kNbJoypads); }
|
||||
|
||||
// The underlying zero-based index for this emulated joypad.
|
||||
constexpr size_t index() const { return index_; }
|
||||
|
||||
// For display and INI purposes, the index is one-based.
|
||||
constexpr size_t ux_index() const { return index_ + 1; }
|
||||
|
||||
constexpr bool operator==(const GameJoy& other) const { return index_ == other.index_; }
|
||||
constexpr bool operator!=(const GameJoy& other) const { return !(*this == other); }
|
||||
constexpr bool operator<(const GameJoy& other) const { return index_ < other.index_; }
|
||||
constexpr bool operator<=(const GameJoy& other) const {
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
constexpr bool operator>(const GameJoy& other) const { return !(*this <= other); }
|
||||
constexpr bool operator>=(const GameJoy& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
const size_t index_;
|
||||
};
|
||||
|
||||
static constexpr std::array<GameJoy, config::kNbJoypads> kAllGameJoys = {
|
||||
GameJoy(0),
|
||||
GameJoy(1),
|
||||
GameJoy(2),
|
||||
GameJoy(3),
|
||||
};
|
||||
|
||||
// A Game Command is represented by a `joypad` number (1 to 4) and a
|
||||
// `game_key`. `joypad` MUST be in the 1-4 range. Debug checks will assert
|
||||
// otherwise.
|
||||
class GameCommand final {
|
||||
public:
|
||||
constexpr GameCommand(GameJoy joypad, GameKey game_key)
|
||||
: joypad_(joypad), game_key_(game_key) {}
|
||||
|
||||
constexpr GameJoy joypad() const { return joypad_; }
|
||||
constexpr GameKey game_key() const { return game_key_; }
|
||||
wxString ToConfigString() const;
|
||||
wxString ToUXString() const;
|
||||
|
||||
constexpr bool operator==(const GameCommand& other) const {
|
||||
return joypad_ == other.joypad_ && game_key_ == other.game_key_;
|
||||
}
|
||||
constexpr bool operator!=(const GameCommand& other) const { return !(*this == other); }
|
||||
constexpr bool operator<(const GameCommand& other) const {
|
||||
if (joypad_ == other.joypad_) {
|
||||
return game_key_ < other.game_key_;
|
||||
} else {
|
||||
return joypad_ < other.joypad_;
|
||||
}
|
||||
}
|
||||
constexpr bool operator<=(const GameCommand& other) const {
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
constexpr bool operator>(const GameCommand& other) const { return !(*this <= other); }
|
||||
constexpr bool operator>=(const GameCommand& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
const GameJoy joypad_;
|
||||
const GameKey game_key_;
|
||||
};
|
||||
|
||||
// A Shortcut Command is represented by the wx command ID.
|
||||
class ShortcutCommand final {
|
||||
public:
|
||||
constexpr explicit ShortcutCommand(int id) : id_(id) {}
|
||||
|
||||
constexpr int id() const { return id_; }
|
||||
wxString ToConfigString() const;
|
||||
|
||||
constexpr bool operator==(const ShortcutCommand& other) const { return id_ == other.id_; }
|
||||
constexpr bool operator!=(const ShortcutCommand& other) const { return !(*this == other); }
|
||||
constexpr bool operator<(const ShortcutCommand& other) const { return id_ < other.id_; }
|
||||
constexpr bool operator<=(const ShortcutCommand& other) const {
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
constexpr bool operator>(const ShortcutCommand& other) const { return !(*this <= other); }
|
||||
constexpr bool operator>=(const ShortcutCommand& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
const int id_;
|
||||
};
|
||||
|
||||
// Conversion utility method. Returns empty string on failure.
|
||||
// This is O(1).
|
||||
wxString GameKeyToString(const GameKey& game_key);
|
||||
|
||||
// Conversion utility method. Returns std::nullopt on failure.
|
||||
// This is O(log(kNbGameKeys)).
|
||||
nonstd::optional<GameKey> StringToGameKey(const wxString& input);
|
||||
|
||||
// Represents a Command for the emulator software. This can be either a Game
|
||||
// Command (a button is pressed on the emulated device) or a Shortcut Command
|
||||
// (a user-specified shortcut is activated).
|
||||
// This is mainly used by the Bindings class to handle assignment in a common
|
||||
// manner and prevent the user from assigning the same input to multiple
|
||||
// Commands, Game or Shortcut.
|
||||
class Command final {
|
||||
public:
|
||||
enum class Tag {
|
||||
kGame = 0,
|
||||
kShortcut,
|
||||
};
|
||||
|
||||
// Converts a string to a Command. Returns std::nullopt on failure.
|
||||
static nonstd::optional<Command> FromString(const wxString& name);
|
||||
|
||||
// Game Command constructor.
|
||||
Command(GameCommand game_control) : tag_(Tag::kGame), control_(game_control) {}
|
||||
|
||||
// Shortcut Command constructors.
|
||||
Command(ShortcutCommand shortcut_control) : tag_(Tag::kShortcut), control_(shortcut_control) {}
|
||||
|
||||
~Command() = default;
|
||||
|
||||
// Returns the type of the value stored by the current object.
|
||||
Tag tag() const { return tag_; }
|
||||
|
||||
bool is_game() const { return tag() == Tag::kGame; }
|
||||
bool is_shortcut() const { return tag() == Tag::kShortcut; }
|
||||
|
||||
const GameCommand& game() const {
|
||||
VBAM_CHECK(is_game());
|
||||
return nonstd::get<GameCommand>(control_);
|
||||
}
|
||||
|
||||
const ShortcutCommand& shortcut() const {
|
||||
VBAM_CHECK(is_shortcut());
|
||||
return nonstd::get<ShortcutCommand>(control_);
|
||||
}
|
||||
|
||||
bool operator==(const Command& other) const {
|
||||
return tag_ == other.tag_ && control_ == other.control_;
|
||||
}
|
||||
bool operator!=(const Command& other) const { return !(*this == other); }
|
||||
bool operator<(const Command& other) const {
|
||||
if (tag_ == other.tag_) {
|
||||
switch (tag_) {
|
||||
case Tag::kGame:
|
||||
return game() < other.game();
|
||||
case Tag::kShortcut:
|
||||
return shortcut() < other.shortcut();
|
||||
}
|
||||
|
||||
VBAM_NOTREACHED();
|
||||
return false;
|
||||
} else {
|
||||
return tag_ < other.tag_;
|
||||
}
|
||||
}
|
||||
bool operator<=(const Command& other) const { return *this < other || *this == other; }
|
||||
bool operator>(const Command& other) const { return !(*this <= other); }
|
||||
bool operator>=(const Command& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
const Tag tag_;
|
||||
const nonstd::variant<GameCommand, ShortcutCommand> control_;
|
||||
};
|
||||
|
||||
} // namespace config
|
||||
|
||||
// Specializations for hash functions for all of the above classes.
|
||||
template <>
|
||||
struct std::hash<config::GameKey> {
|
||||
std::size_t operator()(const config::GameKey& game_key) const noexcept {
|
||||
return std::hash<size_t>{}(static_cast<size_t>(game_key));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::GameJoy> {
|
||||
std::size_t operator()(const config::GameJoy& game_joy) const noexcept {
|
||||
return std::hash<size_t>{}(static_cast<size_t>(game_joy.index()));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::GameCommand> {
|
||||
std::size_t operator()(const config::GameCommand& game_control) const noexcept {
|
||||
const std::size_t hash1 = std::hash<config::GameJoy>{}(game_control.joypad());
|
||||
const std::size_t hash2 = std::hash<config::GameKey>{}(game_control.game_key());
|
||||
return hash1 ^ hash2;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::ShortcutCommand> {
|
||||
std::size_t operator()(const config::ShortcutCommand& shortcut) const noexcept {
|
||||
return std::hash<int>{}(shortcut.id());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<config::Command> {
|
||||
std::size_t operator()(const config::Command& control) const noexcept {
|
||||
switch (control.tag()) {
|
||||
case config::Command::Tag::kGame:
|
||||
return std::hash<config::GameCommand>{}(control.game());
|
||||
case config::Command::Tag::kShortcut:
|
||||
return std::hash<config::ShortcutCommand>{}(control.shortcut());
|
||||
}
|
||||
|
||||
VBAM_NOTREACHED();
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // VBAM_WX_CONFIG_COMMAND_H_
|
@@ -4,16 +4,20 @@ IF(NOT OUTDIR)
|
||||
SET(OUTDIR ".")
|
||||
ENDIF(NOT OUTDIR)
|
||||
|
||||
SET(CMDTAB "${OUTDIR}/cmdtab.cpp")
|
||||
SET(CMDTAB "${OUTDIR}/config/internal/cmdtab.cpp")
|
||||
SET(EVPROTO "${OUTDIR}/cmdhandlers.h")
|
||||
SET(EVTABLE "${OUTDIR}/cmd-evtable.h")
|
||||
|
||||
FILE(READ cmdevents.cpp MW)
|
||||
FILE(READ ../cmdevents.cpp MW)
|
||||
STRING(REGEX MATCHALL "\nEVT_HANDLER([^\")]|\"[^\"]*\")*\\)" MW "${MW}")
|
||||
|
||||
# cmdtab.cpp is a table of cmd-id-name/cmd-name pairs
|
||||
# sorted for binary searching
|
||||
FILE(WRITE "${CMDTAB}" "// Generated from cmdevents.cpp; do not edit\n#include <wx/xrc/xmlres.h>\n\n#include \"wx/wxvbam.h\"\n#include \"wx/wxhead.h\"\n\nstruct cmditem cmdtab[] = {\n")
|
||||
FILE(WRITE "${CMDTAB}" "// Generated from cmdevents.cpp; do not edit\n\n")
|
||||
FILE(APPEND "${CMDTAB}" "#include \"wx/config/cmdtab.h\"\n\n")
|
||||
FILE(APPEND "${CMDTAB}" "#include <wx/xrc/xmlres.h>\n\n")
|
||||
FILE(APPEND "${CMDTAB}" "#include \"wx/config/bindings.h\"\n\n")
|
||||
FILE(APPEND "${CMDTAB}" "std::vector<cmditem> cmdtab = {\n")
|
||||
SET(EVLINES )
|
||||
FOREACH(EV ${MW})
|
||||
# stripping the wxID_ makes it look better, but it's still all-caps
|
||||
@@ -26,7 +30,8 @@ ENDFOREACH(EV)
|
||||
LIST(SORT EVLINES)
|
||||
STRING(REGEX REPLACE ",\n\$" "\n" EVLINES "${EVLINES}")
|
||||
FILE(APPEND "${CMDTAB}" ${EVLINES})
|
||||
FILE(APPEND "${CMDTAB}" "};\nconst int ncmds = sizeof(cmdtab) / sizeof(cmdtab[0]);\n")
|
||||
FILE(APPEND "${CMDTAB}" "};\n")
|
||||
FILE(APPEND "${CMDTAB}" "const int ncmds = sizeof(cmdtab) / sizeof(cmdtab[0]);\n")
|
||||
|
||||
# cmdhandlers.h contains prototypes for all handlers
|
||||
FILE(WRITE "${EVPROTO}" "// Generated from cmdevents.cpp; do not edit\n")
|
148
src/wx/config/emulated-gamepad-test.cpp
Normal file
148
src/wx/config/emulated-gamepad-test.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "wx/config/emulated-gamepad.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <wx/string.h>
|
||||
#include <memory>
|
||||
#include "wx/config/bindings.h"
|
||||
#include "wx/config/command.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
#define KEYM_UP 1 << 6
|
||||
|
||||
// Test fixture to set up the EmulatedGamepad with default bindings.
|
||||
class GamepadTest : public ::testing::Test {
|
||||
public:
|
||||
GamepadTest() = default;
|
||||
~GamepadTest() override = default;
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
gamepad_ =
|
||||
std::make_unique<config::EmulatedGamepad>(std::bind(&GamepadTest::bindings, this));
|
||||
}
|
||||
|
||||
void TearDown() override { gamepad_.reset(); }
|
||||
|
||||
config::Bindings* bindings() { return &bindings_; }
|
||||
config::EmulatedGamepad* gamepad() { return gamepad_.get(); }
|
||||
|
||||
private:
|
||||
config::Bindings bindings_;
|
||||
std::unique_ptr<config::EmulatedGamepad> gamepad_;
|
||||
};
|
||||
|
||||
TEST_F(GamepadTest, Basic) {
|
||||
// No input should be pressed initially.
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
|
||||
const config::JoyInput up(config::JoyId(0), config::JoyControl::HatNorth, 0);
|
||||
|
||||
// Press up, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release up, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
||||
|
||||
// Tests that pressing multiple bindings at the same time keeps the keys pressed
|
||||
// until all corresponding bindings are released.
|
||||
TEST_F(GamepadTest, ManyBindingsPressed) {
|
||||
const config::JoyInput up(config::JoyId(0), config::JoyControl::HatNorth, 0);
|
||||
const config::KeyboardInput w('W');
|
||||
|
||||
// Press up, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Press W, the up key should still be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(w));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release up, the up key should still be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release w, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(w));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
||||
|
||||
// Tests that pressing the same binding twice is a noop.
|
||||
TEST_F(GamepadTest, DoublePress) {
|
||||
const config::JoyInput up(config::JoyId(0), config::JoyControl::HatNorth, 0);
|
||||
|
||||
// Press up, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Press up again, the up key should still be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release up, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
||||
|
||||
// Tests that releasing the same biniding twice is a noop.
|
||||
TEST_F(GamepadTest, DoubleRelease) {
|
||||
const config::JoyInput up(config::JoyId(0), config::JoyControl::HatNorth, 0);
|
||||
|
||||
// Press up, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release up, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
|
||||
// Release up again, the up key should still be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
||||
|
||||
// Tests that pressing an unbound input is a noop.
|
||||
TEST_F(GamepadTest, UnassignedInput) {
|
||||
const config::KeyboardInput f1(WXK_F1);
|
||||
const config::JoyInput up(config::JoyId(0), config::JoyControl::HatNorth, 0);
|
||||
|
||||
// Press F1, nothing should happen.
|
||||
EXPECT_FALSE(gamepad()->OnInputPressed(f1));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
|
||||
// Press up, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release F1, nothing should happen.
|
||||
EXPECT_FALSE(gamepad()->OnInputReleased(f1));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release up, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(up));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
|
||||
// Release F1 again, nothing should happen.
|
||||
EXPECT_FALSE(gamepad()->OnInputReleased(f1));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
||||
|
||||
// Tests that assigning an input to a game command properly triggers the game
|
||||
// command.
|
||||
TEST_F(GamepadTest, NonDefaultInput) {
|
||||
const config::KeyboardInput f1(WXK_F1);
|
||||
|
||||
// Assign F1 to "Up".
|
||||
bindings()->AssignInputToCommand(f1,
|
||||
config::GameCommand(config::GameJoy(0), config::GameKey::Up));
|
||||
|
||||
// Press F1, the up key should be pressed.
|
||||
EXPECT_TRUE(gamepad()->OnInputPressed(f1));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), KEYM_UP);
|
||||
|
||||
// Release F1, the up key should be released.
|
||||
EXPECT_TRUE(gamepad()->OnInputReleased(f1));
|
||||
EXPECT_EQ(gamepad()->GetJoypad(0), 0);
|
||||
}
|
127
src/wx/config/emulated-gamepad.cpp
Normal file
127
src/wx/config/emulated-gamepad.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "wx/config/emulated-gamepad.h"
|
||||
|
||||
#include "core/base/check.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kBitKeyA = (1 << 0);
|
||||
constexpr uint32_t kBitKeyB = (1 << 1);
|
||||
constexpr uint32_t kBitKeySelect = (1 << 2);
|
||||
constexpr uint32_t kBitKeyStart = (1 << 3);
|
||||
constexpr uint32_t kBitKeyRight = (1 << 4);
|
||||
constexpr uint32_t kBitKeyLeft = (1 << 5);
|
||||
constexpr uint32_t kBitKeyUp = (1 << 6);
|
||||
constexpr uint32_t kBitKeyDown = (1 << 7);
|
||||
constexpr uint32_t kBitKeyR = (1 << 8);
|
||||
constexpr uint32_t kBitKeyL = (1 << 9);
|
||||
constexpr uint32_t kBitKeySpeed = (1 << 10);
|
||||
constexpr uint32_t kBitKeyCapture = (1 << 11);
|
||||
constexpr uint32_t kBitKeyGameShark = (1 << 12);
|
||||
constexpr uint32_t kBitKeyAutoA = (1 << 13);
|
||||
constexpr uint32_t kBitKeyAutoB = (1 << 14);
|
||||
constexpr uint32_t kBitKeyMotionUp = (1 << 15);
|
||||
constexpr uint32_t kBitKeyMotionDown = (1 << 16);
|
||||
constexpr uint32_t kBitKeyMotionLeft = (1 << 17);
|
||||
constexpr uint32_t kBitKeyMotionRight = (1 << 18);
|
||||
constexpr uint32_t kBitKeyMotionIn = (1 << 19);
|
||||
constexpr uint32_t kBitKeyMotionOut = (1 << 20);
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array<uint32_t, kNbGameKeys> kBitMask = {
|
||||
kBitKeyUp,
|
||||
kBitKeyDown,
|
||||
kBitKeyLeft,
|
||||
kBitKeyRight,
|
||||
kBitKeyA,
|
||||
kBitKeyB,
|
||||
kBitKeyL,
|
||||
kBitKeyR,
|
||||
kBitKeySelect,
|
||||
kBitKeyStart,
|
||||
kBitKeyMotionUp,
|
||||
kBitKeyMotionDown,
|
||||
kBitKeyMotionLeft,
|
||||
kBitKeyMotionRight,
|
||||
kBitKeyMotionIn,
|
||||
kBitKeyMotionOut,
|
||||
kBitKeyAutoA,
|
||||
kBitKeyAutoB,
|
||||
kBitKeySpeed,
|
||||
kBitKeyCapture,
|
||||
kBitKeyGameShark,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
inline size_t GameKeyToInt(const GameKey& game_key) {
|
||||
return static_cast<size_t>(game_key);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmulatedGamepad::EmulatedGamepad(const BindingsProvider bindings_provider)
|
||||
: joypads_({0, 0, 0, 0}), bindings_provider_(bindings_provider) {}
|
||||
|
||||
bool EmulatedGamepad::OnInputPressed(const config::UserInput& user_input) {
|
||||
VBAM_CHECK(user_input);
|
||||
|
||||
const auto command = bindings_provider_()->CommandForInput(user_input);
|
||||
if (!command || !command->is_game()) {
|
||||
// No associated game control for `user_input`.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the corresponding control.
|
||||
auto iter = active_controls_.find(command->game());
|
||||
if (iter == active_controls_.end()) {
|
||||
iter = active_controls_
|
||||
.insert(std::make_pair(command->game(), std::unordered_set<UserInput>()))
|
||||
.first;
|
||||
}
|
||||
|
||||
iter->second.emplace(user_input);
|
||||
joypads_[command->game().joypad().index()] |=
|
||||
kBitMask[GameKeyToInt(command->game().game_key())];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulatedGamepad::OnInputReleased(const config::UserInput& user_input) {
|
||||
VBAM_CHECK(user_input);
|
||||
|
||||
const auto command = bindings_provider_()->CommandForInput(user_input);
|
||||
if (!command || !command->is_game()) {
|
||||
// No associated game control for `user_input`.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the corresponding control.
|
||||
auto iter = active_controls_.find(command->game());
|
||||
if (iter == active_controls_.end()) {
|
||||
// Double release is noop.
|
||||
return true;
|
||||
}
|
||||
|
||||
iter->second.erase(user_input);
|
||||
if (iter->second.empty()) {
|
||||
// Actually release control.
|
||||
active_controls_.erase(iter);
|
||||
joypads_[command->game().joypad().index()] &=
|
||||
~kBitMask[GameKeyToInt(command->game().game_key())];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulatedGamepad::Reset() {
|
||||
active_controls_.clear();
|
||||
joypads_.fill(0);
|
||||
}
|
||||
|
||||
uint32_t EmulatedGamepad::GetJoypad(size_t joypad) const {
|
||||
if (joypad >= kNbJoypads) {
|
||||
return 0;
|
||||
}
|
||||
return joypads_[joypad];
|
||||
}
|
||||
|
||||
} // namespace config
|
49
src/wx/config/emulated-gamepad.h
Normal file
49
src/wx/config/emulated-gamepad.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
||||
#define VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "wx/config/bindings.h"
|
||||
#include "wx/config/command.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
// Tracks in-game input and computes the joypad value used to send control input
|
||||
// data to the emulator. This class should be kept as a singleton owned by the
|
||||
// application.
|
||||
class EmulatedGamepad final {
|
||||
public:
|
||||
explicit EmulatedGamepad(const BindingsProvider bindings_provider);
|
||||
~EmulatedGamepad() = default;
|
||||
|
||||
// Disable copy constructor and assignment operator.
|
||||
EmulatedGamepad(const EmulatedGamepad&) = delete;
|
||||
EmulatedGamepad& operator=(const EmulatedGamepad&) = delete;
|
||||
|
||||
// Processes `user_input` and updates the internal tracking state.
|
||||
// Returns true if `user_input` corresponds to a game input.
|
||||
bool OnInputPressed(const UserInput& user_input);
|
||||
bool OnInputReleased(const UserInput& user_input);
|
||||
|
||||
// Clears all input.
|
||||
void Reset();
|
||||
|
||||
uint32_t GetJoypad(size_t joypad) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<GameCommand, std::unordered_set<UserInput>> active_controls_;
|
||||
std::array<uint32_t, kNbJoypads> joypads_;
|
||||
const BindingsProvider bindings_provider_;
|
||||
};
|
||||
|
||||
using EmulatedGamepadProvider = std::function<EmulatedGamepad*()>;
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_EMULATED_GAMEPAD_H_
|
@@ -1,297 +0,0 @@
|
||||
#include "wx/config/game-control.h"
|
||||
|
||||
#include "wx/opts.h"
|
||||
#include "wx/strutils.h"
|
||||
#include "wx/wxlogdebug.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kBitKeyA = (1 << 0);
|
||||
constexpr uint32_t kBitKeyB = (1 << 1);
|
||||
constexpr uint32_t kBitKeySelect = (1 << 2);
|
||||
constexpr uint32_t kBitKeyStart = (1 << 3);
|
||||
constexpr uint32_t kBitKeyRight = (1 << 4);
|
||||
constexpr uint32_t kBitKeyLeft = (1 << 5);
|
||||
constexpr uint32_t kBitKeyUp = (1 << 6);
|
||||
constexpr uint32_t kBitKeyDown = (1 << 7);
|
||||
constexpr uint32_t kBitKeyR = (1 << 8);
|
||||
constexpr uint32_t kBitKeyL = (1 << 9);
|
||||
constexpr uint32_t kBitKeySpeed = (1 << 10);
|
||||
constexpr uint32_t kBitKeyCapture = (1 << 11);
|
||||
constexpr uint32_t kBitKeyGameShark = (1 << 12);
|
||||
constexpr uint32_t kBitKeyAutoA = (1 << 13);
|
||||
constexpr uint32_t kBitKeyAutoB = (1 << 14);
|
||||
constexpr uint32_t kBitKeyMotionUp = (1 << 15);
|
||||
constexpr uint32_t kBitKeyMotionDown = (1 << 16);
|
||||
constexpr uint32_t kBitKeyMotionLeft = (1 << 17);
|
||||
constexpr uint32_t kBitKeyMotionRight = (1 << 18);
|
||||
constexpr uint32_t kBitKeyMotionIn = (1 << 19);
|
||||
constexpr uint32_t kBitKeyMotionOut = (1 << 20);
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array<uint32_t, kNbGameKeys> kBitMask = {
|
||||
kBitKeyUp,
|
||||
kBitKeyDown,
|
||||
kBitKeyLeft,
|
||||
kBitKeyRight,
|
||||
kBitKeyA,
|
||||
kBitKeyB,
|
||||
kBitKeyL,
|
||||
kBitKeyR,
|
||||
kBitKeySelect,
|
||||
kBitKeyStart,
|
||||
kBitKeyMotionUp,
|
||||
kBitKeyMotionDown,
|
||||
kBitKeyMotionLeft,
|
||||
kBitKeyMotionRight,
|
||||
kBitKeyMotionIn,
|
||||
kBitKeyMotionOut,
|
||||
kBitKeyAutoA,
|
||||
kBitKeyAutoB,
|
||||
kBitKeySpeed,
|
||||
kBitKeyCapture,
|
||||
kBitKeyGameShark,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
inline int GameKeyToInt(const GameKey& game_key) {
|
||||
return static_cast<int>(game_key);
|
||||
}
|
||||
|
||||
// Returns true if `joypad` is in a valid joypad range.
|
||||
inline bool JoypadInRange(const int& joypad) {
|
||||
constexpr int kMinJoypadIndex = 0;
|
||||
return joypad >= kMinJoypadIndex && joypad < kNbJoypads;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
wxString GameKeyToString(const GameKey& game_key) {
|
||||
// Note: this must match GUI widget names or GUI won't work
|
||||
// This array's order determines tab order as well
|
||||
static const std::array<wxString, kNbGameKeys> kGameKeyStrings = {
|
||||
"Up",
|
||||
"Down",
|
||||
"Left",
|
||||
"Right",
|
||||
"A",
|
||||
"B",
|
||||
"L",
|
||||
"R",
|
||||
"Select",
|
||||
"Start",
|
||||
"MotionUp",
|
||||
"MotionDown",
|
||||
"MotionLeft",
|
||||
"MotionRight",
|
||||
"MotionIn",
|
||||
"MotionOut",
|
||||
"AutoA",
|
||||
"AutoB",
|
||||
"Speed",
|
||||
"Capture",
|
||||
"GS",
|
||||
};
|
||||
return kGameKeyStrings[GameKeyToInt(game_key)];
|
||||
}
|
||||
|
||||
nonstd::optional<GameKey> StringToGameKey(const wxString& input) {
|
||||
static const std::map<wxString, GameKey> kStringToGameKey = {
|
||||
{ "Up", GameKey::Up },
|
||||
{ "Down", GameKey::Down },
|
||||
{ "Left", GameKey::Left },
|
||||
{ "Right", GameKey::Right },
|
||||
{ "A", GameKey::A },
|
||||
{ "B", GameKey::B },
|
||||
{ "L", GameKey::L },
|
||||
{ "R", GameKey::R },
|
||||
{ "Select", GameKey::Select },
|
||||
{ "Start", GameKey::Start },
|
||||
{ "MotionUp", GameKey::MotionUp },
|
||||
{ "MotionDown", GameKey::MotionDown },
|
||||
{ "MotionLeft", GameKey::MotionLeft },
|
||||
{ "MotionRight", GameKey::MotionRight },
|
||||
{ "MotionIn", GameKey::MotionIn },
|
||||
{ "MotionOut", GameKey::MotionOut },
|
||||
{ "AutoA", GameKey::AutoA },
|
||||
{ "AutoB", GameKey::AutoB },
|
||||
{ "Speed", GameKey::Speed },
|
||||
{ "Capture", GameKey::Capture },
|
||||
{ "GS", GameKey::Gameshark },
|
||||
};
|
||||
|
||||
const auto iter = kStringToGameKey.find(input);
|
||||
if (iter == kStringToGameKey.end()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
// static
|
||||
nonstd::optional<GameControl> GameControl::FromString(const wxString& name) {
|
||||
static const wxString kJoypad("Joypad");
|
||||
if (!wxStrncmp(name, kJoypad, kJoypad.size())) {
|
||||
wxLogDebug("Doesn't start with joypad");
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto parts = strutils::split(name, "/");
|
||||
if (parts.size() != 3) {
|
||||
wxLogDebug("Wrong split size: %d", parts.size());
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
const int joypad = parts[1][0] - wxT('1');
|
||||
if (!JoypadInRange(joypad)) {
|
||||
wxLogDebug("Wrong joypad index: %d", joypad);
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
nonstd::optional<GameKey> game_key = StringToGameKey(parts[2]);
|
||||
if (!game_key) {
|
||||
wxLogDebug("Failed to parse game_key: %s", parts[2]);
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return GameControl(joypad, game_key.value());
|
||||
}
|
||||
|
||||
GameControl::GameControl(int joypad, GameKey game_key)
|
||||
: joypad_(joypad),
|
||||
game_key_(game_key),
|
||||
config_string_(wxString::Format("Joypad/%d/%s",
|
||||
joypad_ + 1,
|
||||
GameKeyToString(game_key_))) {
|
||||
assert(JoypadInRange(joypad_));
|
||||
}
|
||||
GameControl::~GameControl() = default;
|
||||
|
||||
bool GameControl::operator==(const GameControl& other) const {
|
||||
return joypad_ == other.joypad_ && game_key_ == other.game_key_;
|
||||
}
|
||||
bool GameControl::operator!=(const GameControl& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool GameControl::operator<(const GameControl& other) const {
|
||||
if (joypad_ != other.joypad_) {
|
||||
return joypad_ < other.joypad_;
|
||||
}
|
||||
if (game_key_ != other.game_key_) {
|
||||
return game_key_ < other.game_key_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool GameControl::operator<=(const GameControl& other) const {
|
||||
return !(*this > other);
|
||||
}
|
||||
bool GameControl::operator>(const GameControl& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
bool GameControl::operator>=(const GameControl& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
GameControlState& GameControlState::Instance() {
|
||||
static GameControlState g_game_control_state;
|
||||
return g_game_control_state;
|
||||
}
|
||||
|
||||
GameControlState::GameControlState() : joypads_({0, 0, 0, 0}) {}
|
||||
GameControlState::~GameControlState() = default;
|
||||
|
||||
bool GameControlState::OnInputPressed(const config::UserInput& user_input) {
|
||||
assert(user_input);
|
||||
|
||||
const auto& game_keys = input_bindings_.find(user_input);
|
||||
if (game_keys == input_bindings_.end()) {
|
||||
// No associated game control for `user_input`.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = keys_pressed_.find(user_input);
|
||||
if (iter != keys_pressed_.end()) {
|
||||
// Double press is noop.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remember the key pressed.
|
||||
keys_pressed_.emplace(user_input);
|
||||
|
||||
// Update all corresponding controls.
|
||||
for (const GameControl& game_control : game_keys->second) {
|
||||
active_controls_[game_control].emplace(user_input);
|
||||
joypads_[game_control.joypad_] |=
|
||||
kBitMask[GameKeyToInt(game_control.game_key_)];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameControlState::OnInputReleased(const config::UserInput& user_input) {
|
||||
assert(user_input);
|
||||
|
||||
const auto& game_keys = input_bindings_.find(user_input);
|
||||
if (game_keys == input_bindings_.end()) {
|
||||
// No associated game control for `user_input`.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = keys_pressed_.find(user_input);
|
||||
if (iter == keys_pressed_.end()) {
|
||||
// Double release is noop.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Release the key pressed.
|
||||
keys_pressed_.erase(iter);
|
||||
|
||||
// Update all corresponding controls.
|
||||
for (const GameControl& game_control : game_keys->second) {
|
||||
auto active_controls = active_controls_.find(game_control);
|
||||
if (active_controls == active_controls_.end()) {
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
active_controls->second.erase(user_input);
|
||||
if (active_controls->second.empty()) {
|
||||
// Actually release control.
|
||||
active_controls_.erase(active_controls);
|
||||
joypads_[game_control.joypad_] &=
|
||||
~kBitMask[GameKeyToInt(game_control.game_key_)];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameControlState::Reset() {
|
||||
active_controls_.clear();
|
||||
keys_pressed_.clear();
|
||||
joypads_.fill(0);
|
||||
}
|
||||
|
||||
void GameControlState::OnGameBindingsChanged() {
|
||||
// We should reset to ensure no key remains accidentally pressed following a
|
||||
// configuration change.
|
||||
Reset();
|
||||
|
||||
input_bindings_.clear();
|
||||
for (const auto& iter : gopts.game_control_bindings) {
|
||||
for (const auto& user_input : iter.second) {
|
||||
input_bindings_[user_input].emplace(iter.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GameControlState::GetJoypad(int joypad) const {
|
||||
assert(JoypadInRange(joypad));
|
||||
return joypads_[joypad];
|
||||
}
|
||||
|
||||
} // namespace config
|
@@ -1,146 +0,0 @@
|
||||
#ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
#define VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <optional.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
// Forward declaration.
|
||||
class GameControlState;
|
||||
|
||||
//clang-format off
|
||||
// Represents an in-game input.
|
||||
enum class GameKey {
|
||||
Up = 0,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
A,
|
||||
B,
|
||||
L,
|
||||
R,
|
||||
Select,
|
||||
Start,
|
||||
MotionUp,
|
||||
MotionDown,
|
||||
MotionLeft,
|
||||
MotionRight,
|
||||
MotionIn,
|
||||
MotionOut,
|
||||
AutoA,
|
||||
AutoB,
|
||||
Speed,
|
||||
Capture,
|
||||
Gameshark,
|
||||
Last = Gameshark
|
||||
};
|
||||
|
||||
inline constexpr int kNbGameKeys = static_cast<size_t>(GameKey::Last) + 1;
|
||||
inline constexpr int kNbJoypads = 4;
|
||||
|
||||
inline constexpr std::array<GameKey, kNbGameKeys> kAllGameKeys = {
|
||||
GameKey::Up,
|
||||
GameKey::Down,
|
||||
GameKey::Left,
|
||||
GameKey::Right,
|
||||
GameKey::A,
|
||||
GameKey::B,
|
||||
GameKey::L,
|
||||
GameKey::R,
|
||||
GameKey::Select,
|
||||
GameKey::Start,
|
||||
GameKey::MotionUp,
|
||||
GameKey::MotionDown,
|
||||
GameKey::MotionLeft,
|
||||
GameKey::MotionRight,
|
||||
GameKey::MotionIn,
|
||||
GameKey::MotionOut,
|
||||
GameKey::AutoA,
|
||||
GameKey::AutoB,
|
||||
GameKey::Speed,
|
||||
GameKey::Capture,
|
||||
GameKey::Gameshark,
|
||||
};
|
||||
//clang-format on
|
||||
|
||||
// Conversion utility method. Returns empty string on failure.
|
||||
// This is O(1).
|
||||
wxString GameKeyToString(const GameKey& game_key);
|
||||
|
||||
// Conversion utility method. Returns std::nullopt on failure.
|
||||
// This is O(log(kNbGameKeys)).
|
||||
nonstd::optional<GameKey> StringToGameKey(const wxString& input);
|
||||
|
||||
// Abstraction for an in-game control, wich is made of a player index (from 0
|
||||
// to 3), and a GameKey.
|
||||
class GameControl {
|
||||
public:
|
||||
// Converts a string to a GameControl. Returns std::nullopt on failure.
|
||||
static nonstd::optional<GameControl> FromString(const wxString& name);
|
||||
|
||||
GameControl(int joypad, GameKey game_key);
|
||||
~GameControl();
|
||||
|
||||
wxString ToString() const { return config_string_; };
|
||||
|
||||
bool operator==(const GameControl& other) const;
|
||||
bool operator!=(const GameControl& other) const;
|
||||
bool operator<(const GameControl& other) const;
|
||||
bool operator<=(const GameControl& other) const;
|
||||
bool operator>(const GameControl& other) const;
|
||||
bool operator>=(const GameControl& other) const;
|
||||
|
||||
private:
|
||||
const int joypad_;
|
||||
const GameKey game_key_;
|
||||
const wxString config_string_;
|
||||
|
||||
friend class GameControlState;
|
||||
};
|
||||
|
||||
// Tracks in-game input and computes the joypad value used to send control input
|
||||
// data to the emulator.
|
||||
class GameControlState {
|
||||
public:
|
||||
// This is a global singleton.
|
||||
static GameControlState& Instance();
|
||||
|
||||
// Disable copy constructor and assignment operator.
|
||||
GameControlState(const GameControlState&) = delete;
|
||||
GameControlState& operator=(const GameControlState&) = delete;
|
||||
|
||||
// Processes `user_input` and updates the internal tracking state.
|
||||
// Returns true if `user_input` corresponds to a game input.
|
||||
bool OnInputPressed(const config::UserInput& user_input);
|
||||
bool OnInputReleased(const config::UserInput& user_input);
|
||||
|
||||
// Clears all input.
|
||||
void Reset();
|
||||
|
||||
// Recomputes internal bindinds. This is a potentially slow operation and
|
||||
// should only be called when the game input configuration has been changed.
|
||||
void OnGameBindingsChanged();
|
||||
|
||||
uint32_t GetJoypad(int joypad) const;
|
||||
|
||||
private:
|
||||
GameControlState();
|
||||
~GameControlState();
|
||||
|
||||
std::map<config::UserInput, std::set<GameControl>> input_bindings_;
|
||||
std::map<GameControl, std::set<config::UserInput>> active_controls_;
|
||||
std::set<config::UserInput> keys_pressed_;
|
||||
std::array<uint32_t, kNbJoypads> joypads_;
|
||||
};
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_GAME_CONTROL_H_
|
615
src/wx/config/internal/bindings-internal.cpp
Normal file
615
src/wx/config/internal/bindings-internal.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
#include "wx/config/bindings.h"
|
||||
#include "wx/config/command.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#define VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
#include "wx/config/internal/bindings-internal.h"
|
||||
#undef VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
|
||||
namespace config {
|
||||
namespace internal {
|
||||
|
||||
const std::unordered_map<Command, std::unordered_set<UserInput>>& DefaultInputs() {
|
||||
// clang-format off
|
||||
static const std::unordered_map<Command, std::unordered_set<UserInput>> kDefaultInputs = {
|
||||
{ShortcutCommand(XRCID("CheatsList")),
|
||||
{
|
||||
KeyboardInput('C', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("NextFrame")),
|
||||
{
|
||||
KeyboardInput('N', wxMOD_CMD)
|
||||
}},
|
||||
// this was annoying people A LOT #334
|
||||
// {ShortcutCommand(wxID_EXIT),
|
||||
// {
|
||||
// KeyboardInput(WXK_ESCAPE)
|
||||
// }},
|
||||
// this was annoying people #298
|
||||
// {ShortcutCommand(wxID_EXIT),
|
||||
// {
|
||||
// KeyboardInput('X', wxMOD_CMD)
|
||||
// }},
|
||||
{ShortcutCommand(wxID_EXIT),
|
||||
{
|
||||
KeyboardInput('Q', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_CLOSE),
|
||||
{
|
||||
KeyboardInput('W', wxMOD_CMD)
|
||||
}},
|
||||
// load most recent is more commonly used than load state
|
||||
// {ShortcutCommand(XRCID("Load")),
|
||||
// {
|
||||
// KeyboardInput('L', wxMOD_CMD)
|
||||
// }},
|
||||
{ShortcutCommand(XRCID("LoadGameRecent")),
|
||||
{
|
||||
KeyboardInput('L', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame01")),
|
||||
{
|
||||
KeyboardInput(WXK_F1)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame02")),
|
||||
{
|
||||
KeyboardInput(WXK_F2)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame03")),
|
||||
{
|
||||
KeyboardInput(WXK_F3)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame04")),
|
||||
{
|
||||
KeyboardInput(WXK_F4)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame05")),
|
||||
{
|
||||
KeyboardInput(WXK_F5)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame06")),
|
||||
{
|
||||
KeyboardInput(WXK_F6)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame07")),
|
||||
{
|
||||
KeyboardInput(WXK_F7)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame08")),
|
||||
{
|
||||
KeyboardInput(WXK_F8)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame09")),
|
||||
{
|
||||
KeyboardInput(WXK_F9)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("LoadGame10")),
|
||||
{
|
||||
KeyboardInput(WXK_F10)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("Pause")),
|
||||
{KeyboardInput(WXK_PAUSE), KeyboardInput('P', wxMOD_CMD)}},
|
||||
{ShortcutCommand(XRCID("Reset")),
|
||||
{
|
||||
KeyboardInput('R', wxMOD_CMD)
|
||||
}},
|
||||
// add shortcuts for original size multiplier #415
|
||||
{ShortcutCommand(XRCID("SetSize1x")),
|
||||
{
|
||||
KeyboardInput('1')
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SetSize2x")),
|
||||
{
|
||||
KeyboardInput('2')
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SetSize3x")),
|
||||
{
|
||||
KeyboardInput('3')
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SetSize4x")),
|
||||
{
|
||||
KeyboardInput('4')
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SetSize5x")),
|
||||
{
|
||||
KeyboardInput('5')
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SetSize6x")),
|
||||
{
|
||||
KeyboardInput('6')
|
||||
}},
|
||||
// save oldest is more commonly used than save other
|
||||
// {ShortcutCommand(XRCID("Save")),
|
||||
// {
|
||||
// KeyboardInput('S', wxMOD_CMD)
|
||||
// }},
|
||||
{ShortcutCommand(XRCID("SaveGameOldest")),
|
||||
{
|
||||
KeyboardInput('S', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame01")),
|
||||
{
|
||||
KeyboardInput(WXK_F1, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame02")),
|
||||
{
|
||||
KeyboardInput(WXK_F2, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame03")),
|
||||
{
|
||||
KeyboardInput(WXK_F3, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame04")),
|
||||
{
|
||||
KeyboardInput(WXK_F4, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame05")),
|
||||
{
|
||||
KeyboardInput(WXK_F5, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame06")),
|
||||
{
|
||||
KeyboardInput(WXK_F6, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame07")),
|
||||
{
|
||||
KeyboardInput(WXK_F7, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame08")),
|
||||
{
|
||||
KeyboardInput(WXK_F8, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame09")),
|
||||
{
|
||||
KeyboardInput(WXK_F9, wxMOD_SHIFT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("SaveGame10")),
|
||||
{
|
||||
KeyboardInput(WXK_F10, wxMOD_SHIFT)
|
||||
}},
|
||||
// I prefer the SDL ESC key binding
|
||||
// {ShortcutCommand(XRCID("ToggleFullscreen")),
|
||||
// {
|
||||
// KeyboardInput(WXK_ESCAPE)
|
||||
// }},
|
||||
// alt-enter is more standard anyway
|
||||
{ShortcutCommand(XRCID("ToggleFullscreen")),
|
||||
{
|
||||
KeyboardInput(WXK_RETURN, wxMOD_ALT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("JoypadAutofireA")),
|
||||
{
|
||||
KeyboardInput('1', wxMOD_ALT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("JoypadAutofireB")),
|
||||
{
|
||||
KeyboardInput('2', wxMOD_ALT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("JoypadAutofireL")),
|
||||
{
|
||||
KeyboardInput('3', wxMOD_ALT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("JoypadAutofireR")),
|
||||
{
|
||||
KeyboardInput('4', wxMOD_ALT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersBG0")),
|
||||
{
|
||||
KeyboardInput('1', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersBG1")),
|
||||
{
|
||||
KeyboardInput('2', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersBG2")),
|
||||
{
|
||||
KeyboardInput('3', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersBG3")),
|
||||
{
|
||||
KeyboardInput('4', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersOBJ")),
|
||||
{
|
||||
KeyboardInput('5', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersWIN0")),
|
||||
{
|
||||
KeyboardInput('6', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersWIN1")),
|
||||
{
|
||||
KeyboardInput('7', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersOBJWIN")),
|
||||
{
|
||||
KeyboardInput('8', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("Rewind")),
|
||||
{
|
||||
KeyboardInput('B', wxMOD_CMD)
|
||||
}},
|
||||
// The following commands do not have the dafault wxWidgets shortcut.
|
||||
// The wxID_FILE1 shortcut is active when the first recent menu entry is populated.
|
||||
// The same goes for the others, wxID_FILE2 is active when the second recent menu entry is
|
||||
// populated, etc.
|
||||
{ShortcutCommand(wxID_FILE1),
|
||||
{
|
||||
KeyboardInput(WXK_F1, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE2),
|
||||
{
|
||||
KeyboardInput(WXK_F2, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE3),
|
||||
{
|
||||
KeyboardInput(WXK_F3, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE4),
|
||||
{
|
||||
KeyboardInput(WXK_F4, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE5),
|
||||
{
|
||||
KeyboardInput(WXK_F5, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE6),
|
||||
{
|
||||
KeyboardInput(WXK_F6, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE7),
|
||||
{
|
||||
KeyboardInput(WXK_F7, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE8),
|
||||
{
|
||||
KeyboardInput(WXK_F8, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE9),
|
||||
{
|
||||
KeyboardInput(WXK_F9, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(wxID_FILE10),
|
||||
{
|
||||
KeyboardInput(WXK_F10, wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("VideoLayersReset")),
|
||||
{
|
||||
KeyboardInput('0', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("ChangeFilter")),
|
||||
{
|
||||
KeyboardInput('G', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("ChangeIFB")),
|
||||
{
|
||||
KeyboardInput('I', wxMOD_CMD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("IncreaseVolume")),
|
||||
{
|
||||
KeyboardInput(WXK_NUMPAD_ADD)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("DecreaseVolume")),
|
||||
{
|
||||
KeyboardInput(WXK_NUMPAD_SUBTRACT)
|
||||
}},
|
||||
{ShortcutCommand(XRCID("ToggleSound")),
|
||||
{
|
||||
KeyboardInput(WXK_NUMPAD_ENTER)
|
||||
}},
|
||||
|
||||
// Player 1 controls.
|
||||
{GameCommand(GameJoy(0), config::GameKey::Up),
|
||||
{
|
||||
KeyboardInput('W'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 11),
|
||||
JoyInput(JoyId(0), JoyControl::AxisMinus, 1),
|
||||
JoyInput(JoyId(0), JoyControl::AxisMinus, 3),
|
||||
JoyInput(JoyId(0), JoyControl::HatNorth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Down),
|
||||
{
|
||||
KeyboardInput('S'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 12),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 1),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 3),
|
||||
JoyInput(JoyId(0), JoyControl::HatSouth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Left),
|
||||
{
|
||||
KeyboardInput('A'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 13),
|
||||
JoyInput(JoyId(0), JoyControl::AxisMinus, 0),
|
||||
JoyInput(JoyId(0), JoyControl::AxisMinus, 2),
|
||||
JoyInput(JoyId(0), JoyControl::HatWest, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Right),
|
||||
{
|
||||
KeyboardInput('D'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 14),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 0),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 2),
|
||||
JoyInput(JoyId(0), JoyControl::HatEast, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::A),
|
||||
{
|
||||
KeyboardInput('L'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 1),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::B),
|
||||
{
|
||||
KeyboardInput('K'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::L),
|
||||
{
|
||||
KeyboardInput('I'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 2),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 9),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::R),
|
||||
{
|
||||
KeyboardInput('O'),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 3),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 10),
|
||||
JoyInput(JoyId(0), JoyControl::AxisPlus, 5),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Select),
|
||||
{
|
||||
KeyboardInput(WXK_BACK),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Start),
|
||||
{
|
||||
KeyboardInput(WXK_RETURN),
|
||||
JoyInput(JoyId(0), JoyControl::Button, 6),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionUp), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionDown), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionLeft), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionRight), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionIn), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::MotionOut), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::AutoA), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::AutoB), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Speed),
|
||||
{
|
||||
KeyboardInput(WXK_SPACE),
|
||||
}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Capture), {}},
|
||||
{GameCommand(GameJoy(0), config::GameKey::Gameshark), {}},
|
||||
|
||||
// Player 2 controls.
|
||||
{GameCommand(GameJoy(1), config::GameKey::Up),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 11),
|
||||
JoyInput(JoyId(1), JoyControl::AxisMinus, 1),
|
||||
JoyInput(JoyId(1), JoyControl::AxisMinus, 3),
|
||||
JoyInput(JoyId(1), JoyControl::HatNorth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Down),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 12),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 1),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 3),
|
||||
JoyInput(JoyId(1), JoyControl::HatSouth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Left),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 13),
|
||||
JoyInput(JoyId(1), JoyControl::AxisMinus, 0),
|
||||
JoyInput(JoyId(1), JoyControl::AxisMinus, 2),
|
||||
JoyInput(JoyId(1), JoyControl::HatWest, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Right),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 14),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 0),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 2),
|
||||
JoyInput(JoyId(1), JoyControl::HatEast, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::A),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 1),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::B),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::L),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 2),
|
||||
JoyInput(JoyId(1), JoyControl::Button, 9),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::R),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 3),
|
||||
JoyInput(JoyId(1), JoyControl::Button, 10),
|
||||
JoyInput(JoyId(1), JoyControl::AxisPlus, 5),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Select),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Start),
|
||||
{
|
||||
JoyInput(JoyId(1), JoyControl::Button, 6),
|
||||
}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionUp), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionDown), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionLeft), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionRight), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionIn), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::MotionOut), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::AutoA), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::AutoB), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Speed), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Capture), {}},
|
||||
{GameCommand(GameJoy(1), config::GameKey::Gameshark), {}},
|
||||
|
||||
// Player 3 controls.
|
||||
{GameCommand(GameJoy(2), config::GameKey::Up),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 11),
|
||||
JoyInput(JoyId(2), JoyControl::AxisMinus, 1),
|
||||
JoyInput(JoyId(2), JoyControl::AxisMinus, 3),
|
||||
JoyInput(JoyId(2), JoyControl::HatNorth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Down),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 12),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 1),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 3),
|
||||
JoyInput(JoyId(2), JoyControl::HatSouth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Left),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 13),
|
||||
JoyInput(JoyId(2), JoyControl::AxisMinus, 0),
|
||||
JoyInput(JoyId(2), JoyControl::AxisMinus, 2),
|
||||
JoyInput(JoyId(2), JoyControl::HatWest, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Right),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 14),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 0),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 2),
|
||||
JoyInput(JoyId(2), JoyControl::HatEast, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::A),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 1),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::B),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::L),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 2),
|
||||
JoyInput(JoyId(2), JoyControl::Button, 9),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::R),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 3),
|
||||
JoyInput(JoyId(2), JoyControl::Button, 10),
|
||||
JoyInput(JoyId(2), JoyControl::AxisPlus, 5),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Select),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Start),
|
||||
{
|
||||
JoyInput(JoyId(2), JoyControl::Button, 6),
|
||||
}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionUp), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionDown), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionLeft), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionRight), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionIn), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::MotionOut), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::AutoA), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::AutoB), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Speed), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Capture), {}},
|
||||
{GameCommand(GameJoy(2), config::GameKey::Gameshark), {}},
|
||||
|
||||
// Player 4 controls.
|
||||
{GameCommand(GameJoy(3), config::GameKey::Up),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 11),
|
||||
JoyInput(JoyId(3), JoyControl::AxisMinus, 1),
|
||||
JoyInput(JoyId(3), JoyControl::AxisMinus, 3),
|
||||
JoyInput(JoyId(3), JoyControl::HatNorth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Down),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 12),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 1),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 3),
|
||||
JoyInput(JoyId(3), JoyControl::HatSouth, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Left),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 13),
|
||||
JoyInput(JoyId(3), JoyControl::AxisMinus, 0),
|
||||
JoyInput(JoyId(3), JoyControl::AxisMinus, 2),
|
||||
JoyInput(JoyId(3), JoyControl::HatWest, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Right),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 14),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 0),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 2),
|
||||
JoyInput(JoyId(3), JoyControl::HatEast, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::A),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 1),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::B),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 0),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::L),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 2),
|
||||
JoyInput(JoyId(3), JoyControl::Button, 9),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::R),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 3),
|
||||
JoyInput(JoyId(3), JoyControl::Button, 10),
|
||||
JoyInput(JoyId(3), JoyControl::AxisPlus, 5),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Select),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 4),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Start),
|
||||
{
|
||||
JoyInput(JoyId(3), JoyControl::Button, 6),
|
||||
}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionUp), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionDown), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionLeft), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionRight), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionIn), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::MotionOut), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::AutoA), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::AutoB), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Speed), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Capture), {}},
|
||||
{GameCommand(GameJoy(3), config::GameKey::Gameshark), {}},
|
||||
};
|
||||
// clang-format on
|
||||
return kDefaultInputs;
|
||||
}
|
||||
|
||||
const std::unordered_set<UserInput>& DefaultInputsForCommand(const Command& command) {
|
||||
const auto& iter = DefaultInputs().find(command);
|
||||
if (iter != DefaultInputs().end()) {
|
||||
return iter->second;
|
||||
}
|
||||
static const std::unordered_set<UserInput> kEmptySet;
|
||||
return kEmptySet;
|
||||
}
|
||||
|
||||
bool IsDefaultInputForCommand(const Command& command, const UserInput& input) {
|
||||
const auto& iter = DefaultInputs().find(command);
|
||||
if (iter != DefaultInputs().end()) {
|
||||
return iter->second.find(input) != iter->second.end();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace config
|
118
src/wx/config/internal/bindings-internal.h
Normal file
118
src/wx/config/internal/bindings-internal.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef VBAM_BINDINGS_INTERNAL_INCLUDE
|
||||
#error "Do not include "config/internal/bindings-internal.h" outside of the implementation."
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "wx/config/command.h"
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace config {
|
||||
namespace internal {
|
||||
|
||||
// Returns the map of commands to their default inputs.
|
||||
const std::unordered_map<Command, std::unordered_set<UserInput>>& DefaultInputs();
|
||||
|
||||
// Returns the default inputs for the given `command`.
|
||||
// Returns an empty set if there are no default inputs for `command`.
|
||||
const std::unordered_set<UserInput>& DefaultInputsForCommand(const Command& command);
|
||||
|
||||
// Returns true if `input` is the default input for `command`.
|
||||
bool IsDefaultInputForCommand(const Command& command, const UserInput& input);
|
||||
|
||||
// clang-format off
|
||||
static constexpr std::array<GameCommand, kNbGameKeys * kNbJoypads> kOrderedGameCommands = {
|
||||
GameCommand(GameJoy(0), GameKey::Up),
|
||||
GameCommand(GameJoy(0), GameKey::Down),
|
||||
GameCommand(GameJoy(0), GameKey::Left),
|
||||
GameCommand(GameJoy(0), GameKey::Right),
|
||||
GameCommand(GameJoy(0), GameKey::A),
|
||||
GameCommand(GameJoy(0), GameKey::B),
|
||||
GameCommand(GameJoy(0), GameKey::L),
|
||||
GameCommand(GameJoy(0), GameKey::R),
|
||||
GameCommand(GameJoy(0), GameKey::Select),
|
||||
GameCommand(GameJoy(0), GameKey::Start),
|
||||
GameCommand(GameJoy(0), GameKey::MotionUp),
|
||||
GameCommand(GameJoy(0), GameKey::MotionDown),
|
||||
GameCommand(GameJoy(0), GameKey::MotionLeft),
|
||||
GameCommand(GameJoy(0), GameKey::MotionRight),
|
||||
GameCommand(GameJoy(0), GameKey::MotionIn),
|
||||
GameCommand(GameJoy(0), GameKey::MotionOut),
|
||||
GameCommand(GameJoy(0), GameKey::AutoA),
|
||||
GameCommand(GameJoy(0), GameKey::AutoB),
|
||||
GameCommand(GameJoy(0), GameKey::Speed),
|
||||
GameCommand(GameJoy(0), GameKey::Capture),
|
||||
GameCommand(GameJoy(0), GameKey::Gameshark),
|
||||
|
||||
GameCommand(GameJoy(1), GameKey::Up),
|
||||
GameCommand(GameJoy(1), GameKey::Down),
|
||||
GameCommand(GameJoy(1), GameKey::Left),
|
||||
GameCommand(GameJoy(1), GameKey::Right),
|
||||
GameCommand(GameJoy(1), GameKey::A),
|
||||
GameCommand(GameJoy(1), GameKey::B),
|
||||
GameCommand(GameJoy(1), GameKey::L),
|
||||
GameCommand(GameJoy(1), GameKey::R),
|
||||
GameCommand(GameJoy(1), GameKey::Select),
|
||||
GameCommand(GameJoy(1), GameKey::Start),
|
||||
GameCommand(GameJoy(1), GameKey::MotionUp),
|
||||
GameCommand(GameJoy(1), GameKey::MotionDown),
|
||||
GameCommand(GameJoy(1), GameKey::MotionLeft),
|
||||
GameCommand(GameJoy(1), GameKey::MotionRight),
|
||||
GameCommand(GameJoy(1), GameKey::MotionIn),
|
||||
GameCommand(GameJoy(1), GameKey::MotionOut),
|
||||
GameCommand(GameJoy(1), GameKey::AutoA),
|
||||
GameCommand(GameJoy(1), GameKey::AutoB),
|
||||
GameCommand(GameJoy(1), GameKey::Speed),
|
||||
GameCommand(GameJoy(1), GameKey::Capture),
|
||||
GameCommand(GameJoy(1), GameKey::Gameshark),
|
||||
|
||||
GameCommand(GameJoy(2), GameKey::Up),
|
||||
GameCommand(GameJoy(2), GameKey::Down),
|
||||
GameCommand(GameJoy(2), GameKey::Left),
|
||||
GameCommand(GameJoy(2), GameKey::Right),
|
||||
GameCommand(GameJoy(2), GameKey::A),
|
||||
GameCommand(GameJoy(2), GameKey::B),
|
||||
GameCommand(GameJoy(2), GameKey::L),
|
||||
GameCommand(GameJoy(2), GameKey::R),
|
||||
GameCommand(GameJoy(2), GameKey::Select),
|
||||
GameCommand(GameJoy(2), GameKey::Start),
|
||||
GameCommand(GameJoy(2), GameKey::MotionUp),
|
||||
GameCommand(GameJoy(2), GameKey::MotionDown),
|
||||
GameCommand(GameJoy(2), GameKey::MotionLeft),
|
||||
GameCommand(GameJoy(2), GameKey::MotionRight),
|
||||
GameCommand(GameJoy(2), GameKey::MotionIn),
|
||||
GameCommand(GameJoy(2), GameKey::MotionOut),
|
||||
GameCommand(GameJoy(2), GameKey::AutoA),
|
||||
GameCommand(GameJoy(2), GameKey::AutoB),
|
||||
GameCommand(GameJoy(2), GameKey::Speed),
|
||||
GameCommand(GameJoy(2), GameKey::Capture),
|
||||
GameCommand(GameJoy(2), GameKey::Gameshark),
|
||||
|
||||
GameCommand(GameJoy(3), GameKey::Up),
|
||||
GameCommand(GameJoy(3), GameKey::Down),
|
||||
GameCommand(GameJoy(3), GameKey::Left),
|
||||
GameCommand(GameJoy(3), GameKey::Right),
|
||||
GameCommand(GameJoy(3), GameKey::A),
|
||||
GameCommand(GameJoy(3), GameKey::B),
|
||||
GameCommand(GameJoy(3), GameKey::L),
|
||||
GameCommand(GameJoy(3), GameKey::R),
|
||||
GameCommand(GameJoy(3), GameKey::Select),
|
||||
GameCommand(GameJoy(3), GameKey::Start),
|
||||
GameCommand(GameJoy(3), GameKey::MotionUp),
|
||||
GameCommand(GameJoy(3), GameKey::MotionDown),
|
||||
GameCommand(GameJoy(3), GameKey::MotionLeft),
|
||||
GameCommand(GameJoy(3), GameKey::MotionRight),
|
||||
GameCommand(GameJoy(3), GameKey::MotionIn),
|
||||
GameCommand(GameJoy(3), GameKey::MotionOut),
|
||||
GameCommand(GameJoy(3), GameKey::AutoA),
|
||||
GameCommand(GameJoy(3), GameKey::AutoB),
|
||||
GameCommand(GameJoy(3), GameKey::Speed),
|
||||
GameCommand(GameJoy(3), GameKey::Capture),
|
||||
GameCommand(GameJoy(3), GameKey::Gameshark),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
} // namespace internal
|
||||
} // namespace config
|
@@ -6,10 +6,12 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <wx/translation.h>
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "core/base/system.h"
|
||||
#include "core/gb/gbGlobals.h"
|
||||
#include "core/gba/gbaSound.h"
|
||||
@@ -326,9 +328,10 @@ std::array<Option, kNbOptions>& Option::All() {
|
||||
Option(OptionID::kPrefSkipSaveGameCheats, &coreOptions.skipSaveGameCheats, 0, 1),
|
||||
Option(OptionID::kPrefSkipSaveGameBattery, &coreOptions.skipSaveGameBattery, 0, 1),
|
||||
Option(OptionID::kPrefThrottle, &coreOptions.throttle, 0, 450),
|
||||
Option(OptionID::kPrefSpeedupThrottle, &coreOptions.speedup_throttle, 0, 3000),
|
||||
Option(OptionID::kPrefSpeedupFrameSkip, &coreOptions.speedup_frame_skip, 0, 300),
|
||||
Option(OptionID::kPrefSpeedupThrottle, &coreOptions.speedup_throttle, 0, 450),
|
||||
Option(OptionID::kPrefSpeedupFrameSkip, &coreOptions.speedup_frame_skip, 0, 40),
|
||||
Option(OptionID::kPrefSpeedupThrottleFrameSkip, &coreOptions.speedup_throttle_frame_skip),
|
||||
Option(OptionID::kPrefSpeedupMute, &coreOptions.speedup_mute),
|
||||
Option(OptionID::kPrefUseBiosGB, &g_owned_opts.use_bios_file_gb),
|
||||
Option(OptionID::kPrefUseBiosGBA, &g_owned_opts.use_bios_file_gba),
|
||||
Option(OptionID::kPrefUseBiosGBC, &g_owned_opts.use_bios_file_gbc),
|
||||
@@ -529,6 +532,8 @@ const std::array<OptionData, kNbOptions + 1> kAllOptionsData = {
|
||||
"throttle)")},
|
||||
OptionData{"preferences/speedupThrottleFrameSkip", "",
|
||||
_("Use frame skip for speedup throttle")},
|
||||
OptionData{"preferences/speedupMute", "",
|
||||
_("Mute sound during speedup")},
|
||||
OptionData{"preferences/useBiosGB", "BootRomGB", _("Use the specified BIOS file for Game Boy")},
|
||||
OptionData{"preferences/useBiosGBA", "BootRomEn", _("Use the specified BIOS file")},
|
||||
OptionData{"preferences/useBiosGBC", "BootRomGBC",
|
||||
@@ -579,14 +584,14 @@ const std::array<OptionData, kNbOptions + 1> kAllOptionsData = {
|
||||
};
|
||||
|
||||
nonstd::optional<OptionID> StringToOptionId(const wxString& input) {
|
||||
static std::map<wxString, OptionID> kStringToOptionId;
|
||||
if (kStringToOptionId.empty()) {
|
||||
static const std::map<wxString, OptionID> kStringToOptionId([] {
|
||||
std::map<wxString, OptionID> string_to_option_id;
|
||||
for (size_t i = 0; i < kNbOptions; i++) {
|
||||
kStringToOptionId.emplace(kAllOptionsData[i].config_name,
|
||||
static_cast<OptionID>(i));
|
||||
}
|
||||
assert(kStringToOptionId.size() == kNbOptions);
|
||||
string_to_option_id.emplace(kAllOptionsData[i].config_name, static_cast<OptionID>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_option_id.size() == kNbOptions);
|
||||
return string_to_option_id;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToOptionId.find(input);
|
||||
if (iter == kStringToOptionId.end()) {
|
||||
@@ -597,42 +602,43 @@ nonstd::optional<OptionID> StringToOptionId(const wxString& input) {
|
||||
|
||||
wxString FilterToString(const Filter& value) {
|
||||
const size_t size_value = static_cast<size_t>(value);
|
||||
assert(size_value < kNbFilters);
|
||||
VBAM_CHECK(size_value < kNbFilters);
|
||||
return kFilterStrings[size_value];
|
||||
}
|
||||
|
||||
wxString InterframeToString(const Interframe& value) {
|
||||
const size_t size_value = static_cast<size_t>(value);
|
||||
assert(size_value < kNbInterframes);
|
||||
VBAM_CHECK(size_value < kNbInterframes);
|
||||
return kInterframeStrings[size_value];
|
||||
}
|
||||
|
||||
wxString RenderMethodToString(const RenderMethod& value) {
|
||||
const size_t size_value = static_cast<size_t>(value);
|
||||
assert(size_value < kNbRenderMethods);
|
||||
VBAM_CHECK(size_value < kNbRenderMethods);
|
||||
return kRenderMethodStrings[size_value];
|
||||
}
|
||||
|
||||
wxString AudioApiToString(const AudioApi& value) {
|
||||
const size_t size_value = static_cast<size_t>(value);
|
||||
assert(size_value < kNbAudioApis);
|
||||
VBAM_CHECK(size_value < kNbAudioApis);
|
||||
return kAudioApiStrings[size_value];
|
||||
}
|
||||
|
||||
wxString AudioRateToString(const AudioRate& value) {
|
||||
const size_t size_value = static_cast<size_t>(value);
|
||||
assert(size_value < kNbSoundRate);
|
||||
VBAM_CHECK(size_value < kNbSoundRate);
|
||||
return kAudioRateStrings[size_value];
|
||||
}
|
||||
|
||||
Filter StringToFilter(const wxString& config_name, const wxString& input) {
|
||||
static std::map<wxString, Filter> kStringToFilter;
|
||||
if (kStringToFilter.empty()) {
|
||||
static const std::map<wxString, Filter> kStringToFilter([] {
|
||||
std::map<wxString, Filter> string_to_filter;
|
||||
for (size_t i = 0; i < kNbFilters; i++) {
|
||||
kStringToFilter.emplace(kFilterStrings[i], static_cast<Filter>(i));
|
||||
}
|
||||
assert(kStringToFilter.size() == kNbFilters);
|
||||
string_to_filter.emplace(kFilterStrings[i], static_cast<Filter>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_filter.size() == kNbFilters);
|
||||
return string_to_filter;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToFilter.find(input);
|
||||
if (iter == kStringToFilter.end()) {
|
||||
@@ -645,14 +651,14 @@ Filter StringToFilter(const wxString& config_name, const wxString& input) {
|
||||
}
|
||||
|
||||
Interframe StringToInterframe(const wxString& config_name, const wxString& input) {
|
||||
static std::map<wxString, Interframe> kStringToInterframe;
|
||||
if (kStringToInterframe.empty()) {
|
||||
static const std::map<wxString, Interframe> kStringToInterframe([] {
|
||||
std::map<wxString, Interframe> string_to_interframe;
|
||||
for (size_t i = 0; i < kNbInterframes; i++) {
|
||||
kStringToInterframe.emplace(kInterframeStrings[i],
|
||||
static_cast<Interframe>(i));
|
||||
}
|
||||
assert(kStringToInterframe.size() == kNbInterframes);
|
||||
string_to_interframe.emplace(kInterframeStrings[i], static_cast<Interframe>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_interframe.size() == kNbInterframes);
|
||||
return string_to_interframe;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToInterframe.find(input);
|
||||
if (iter == kStringToInterframe.end()) {
|
||||
@@ -666,14 +672,14 @@ Interframe StringToInterframe(const wxString& config_name, const wxString& input
|
||||
|
||||
RenderMethod StringToRenderMethod(const wxString& config_name,
|
||||
const wxString& input) {
|
||||
static std::map<wxString, RenderMethod> kStringToRenderMethod;
|
||||
if (kStringToRenderMethod.empty()) {
|
||||
static const std::map<wxString, RenderMethod> kStringToRenderMethod([] {
|
||||
std::map<wxString, RenderMethod> string_to_render_method;
|
||||
for (size_t i = 0; i < kNbRenderMethods; i++) {
|
||||
kStringToRenderMethod.emplace(kRenderMethodStrings[i],
|
||||
static_cast<RenderMethod>(i));
|
||||
}
|
||||
assert(kStringToRenderMethod.size() == kNbRenderMethods);
|
||||
string_to_render_method.emplace(kRenderMethodStrings[i], static_cast<RenderMethod>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_render_method.size() == kNbRenderMethods);
|
||||
return string_to_render_method;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToRenderMethod.find(input);
|
||||
if (iter == kStringToRenderMethod.end()) {
|
||||
@@ -686,14 +692,14 @@ RenderMethod StringToRenderMethod(const wxString& config_name,
|
||||
}
|
||||
|
||||
AudioApi StringToAudioApi(const wxString& config_name, const wxString& input) {
|
||||
static std::map<wxString, AudioApi> kStringToAudioApi;
|
||||
if (kStringToAudioApi.empty()) {
|
||||
static const std::map<wxString, AudioApi> kStringToAudioApi([] {
|
||||
std::map<wxString, AudioApi> string_to_audio_api;
|
||||
for (size_t i = 0; i < kNbAudioApis; i++) {
|
||||
kStringToAudioApi.emplace(kAudioApiStrings[i],
|
||||
static_cast<AudioApi>(i));
|
||||
}
|
||||
assert(kStringToAudioApi.size() == kNbAudioApis);
|
||||
string_to_audio_api.emplace(kAudioApiStrings[i], static_cast<AudioApi>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_audio_api.size() == kNbAudioApis);
|
||||
return string_to_audio_api;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToAudioApi.find(input);
|
||||
if (iter == kStringToAudioApi.end()) {
|
||||
@@ -706,13 +712,14 @@ AudioApi StringToAudioApi(const wxString& config_name, const wxString& input) {
|
||||
}
|
||||
|
||||
AudioRate StringToSoundQuality(const wxString& config_name, const wxString& input) {
|
||||
static std::map<wxString, AudioRate> kStringToSoundQuality;
|
||||
if (kStringToSoundQuality.empty()) {
|
||||
static const std::map<wxString, AudioRate> kStringToSoundQuality([] {
|
||||
std::map<wxString, AudioRate> string_to_sound_quality;
|
||||
for (size_t i = 0; i < kNbSoundRate; i++) {
|
||||
kStringToSoundQuality.emplace(kAudioRateStrings[i], static_cast<AudioRate>(i));
|
||||
}
|
||||
assert(kStringToSoundQuality.size() == kNbSoundRate);
|
||||
string_to_sound_quality.emplace(kAudioRateStrings[i], static_cast<AudioRate>(i));
|
||||
}
|
||||
VBAM_CHECK(string_to_sound_quality.size() == kNbSoundRate);
|
||||
return string_to_sound_quality;
|
||||
}());
|
||||
|
||||
const auto iter = kStringToSoundQuality.find(input);
|
||||
if (iter == kStringToSoundQuality.end()) {
|
||||
@@ -726,28 +733,23 @@ AudioRate StringToSoundQuality(const wxString& config_name, const wxString& inpu
|
||||
wxString AllEnumValuesForType(Option::Type type) {
|
||||
switch (type) {
|
||||
case Option::Type::kFilter: {
|
||||
static const wxString kAllFilterValues =
|
||||
AllEnumValuesForArray(kFilterStrings);
|
||||
static const wxString kAllFilterValues(AllEnumValuesForArray(kFilterStrings));
|
||||
return kAllFilterValues;
|
||||
}
|
||||
case Option::Type::kInterframe: {
|
||||
static const wxString kAllInterframeValues =
|
||||
AllEnumValuesForArray(kInterframeStrings);
|
||||
static const wxString kAllInterframeValues(AllEnumValuesForArray(kInterframeStrings));
|
||||
return kAllInterframeValues;
|
||||
}
|
||||
case Option::Type::kRenderMethod: {
|
||||
static const wxString kAllRenderValues =
|
||||
AllEnumValuesForArray(kRenderMethodStrings);
|
||||
static const wxString kAllRenderValues(AllEnumValuesForArray(kRenderMethodStrings));
|
||||
return kAllRenderValues;
|
||||
}
|
||||
case Option::Type::kAudioApi: {
|
||||
static const wxString kAllAudioApiValues =
|
||||
AllEnumValuesForArray(kAudioApiStrings);
|
||||
static const wxString kAllAudioApiValues(AllEnumValuesForArray(kAudioApiStrings));
|
||||
return kAllAudioApiValues;
|
||||
}
|
||||
case Option::Type::kAudioRate: {
|
||||
static const wxString kAllSoundQualityValues =
|
||||
AllEnumValuesForArray(kAudioRateStrings);
|
||||
static const wxString kAllSoundQualityValues(AllEnumValuesForArray(kAudioRateStrings));
|
||||
return kAllSoundQualityValues;
|
||||
}
|
||||
|
||||
@@ -760,10 +762,10 @@ wxString AllEnumValuesForType(Option::Type type) {
|
||||
case Option::Type::kUnsigned:
|
||||
case Option::Type::kString:
|
||||
case Option::Type::kGbPalette:
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return wxEmptyString;
|
||||
}
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
@@ -789,10 +791,10 @@ size_t MaxForType(Option::Type type) {
|
||||
case Option::Type::kUnsigned:
|
||||
case Option::Type::kString:
|
||||
case Option::Type::kGbPalette:
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return 0;
|
||||
}
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -1,107 +0,0 @@
|
||||
#include "wx/config/shortcuts.h"
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#define VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
#include "wx/config/internal/shortcuts-internal.h"
|
||||
#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
|
||||
namespace config {
|
||||
namespace internal {
|
||||
|
||||
const std::unordered_map<int, UserInput>& DefaultShortcuts() {
|
||||
static const std::unordered_map<int, UserInput> kDefaultShortcuts = {
|
||||
{XRCID("CheatsList"), UserInput('C', wxMOD_CMD)},
|
||||
{XRCID("NextFrame"), UserInput('N', wxMOD_CMD)},
|
||||
// this was annoying people A LOT #334
|
||||
//{wxID_EXIT, UserInput(WXK_ESCAPE, wxMOD_NONE)},
|
||||
// this was annoying people #298
|
||||
//{wxID_EXIT, UserInput('X', wxMOD_CMD)},
|
||||
|
||||
{wxID_EXIT, UserInput('Q', wxMOD_CMD)},
|
||||
{wxID_CLOSE, UserInput('W', wxMOD_CMD)},
|
||||
// load most recent is more commonly used than load state
|
||||
// {XRCID("Load"), UserInput('L', wxMOD_CMD)},
|
||||
{XRCID("LoadGameRecent"), UserInput('L', wxMOD_CMD)},
|
||||
{XRCID("LoadGame01"), UserInput(WXK_F1, wxMOD_NONE)},
|
||||
{XRCID("LoadGame02"), UserInput(WXK_F2, wxMOD_NONE)},
|
||||
{XRCID("LoadGame03"), UserInput(WXK_F3, wxMOD_NONE)},
|
||||
{XRCID("LoadGame04"), UserInput(WXK_F4, wxMOD_NONE)},
|
||||
{XRCID("LoadGame05"), UserInput(WXK_F5, wxMOD_NONE)},
|
||||
{XRCID("LoadGame06"), UserInput(WXK_F6, wxMOD_NONE)},
|
||||
{XRCID("LoadGame07"), UserInput(WXK_F7, wxMOD_NONE)},
|
||||
{XRCID("LoadGame08"), UserInput(WXK_F8, wxMOD_NONE)},
|
||||
{XRCID("LoadGame09"), UserInput(WXK_F9, wxMOD_NONE)},
|
||||
{XRCID("LoadGame10"), UserInput(WXK_F10, wxMOD_NONE)},
|
||||
{XRCID("Pause"), UserInput(WXK_PAUSE, wxMOD_NONE)},
|
||||
{XRCID("Pause"), UserInput('P', wxMOD_CMD)},
|
||||
{XRCID("Reset"), UserInput('R', wxMOD_CMD)},
|
||||
// add shortcuts for original size multiplier #415
|
||||
{XRCID("SetSize1x"), UserInput('1', wxMOD_NONE)},
|
||||
{XRCID("SetSize2x"), UserInput('2', wxMOD_NONE)},
|
||||
{XRCID("SetSize3x"), UserInput('3', wxMOD_NONE)},
|
||||
{XRCID("SetSize4x"), UserInput('4', wxMOD_NONE)},
|
||||
{XRCID("SetSize5x"), UserInput('5', wxMOD_NONE)},
|
||||
{XRCID("SetSize6x"), UserInput('6', wxMOD_NONE)},
|
||||
// save oldest is more commonly used than save other
|
||||
// {XRCID("Save"), UserInput('S', wxMOD_CMD)},
|
||||
{XRCID("SaveGameOldest"), UserInput('S', wxMOD_CMD)},
|
||||
{XRCID("SaveGame01"), UserInput(WXK_F1, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame02"), UserInput(WXK_F2, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame03"), UserInput(WXK_F3, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame04"), UserInput(WXK_F4, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame05"), UserInput(WXK_F5, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame06"), UserInput(WXK_F6, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame07"), UserInput(WXK_F7, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame08"), UserInput(WXK_F8, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame09"), UserInput(WXK_F9, wxMOD_SHIFT)},
|
||||
{XRCID("SaveGame10"), UserInput(WXK_F10, wxMOD_SHIFT)},
|
||||
// I prefer the SDL ESC key binding
|
||||
// {XRCID("ToggleFullscreen"), UserInput(WXK_ESCAPE, wxMOD_NONE)},
|
||||
// alt-enter is more standard anyway
|
||||
{XRCID("ToggleFullscreen"), UserInput(WXK_RETURN, wxMOD_ALT)},
|
||||
{XRCID("JoypadAutofireA"), UserInput('1', wxMOD_ALT)},
|
||||
{XRCID("JoypadAutofireB"), UserInput('2', wxMOD_ALT)},
|
||||
{XRCID("JoypadAutofireL"), UserInput('3', wxMOD_ALT)},
|
||||
{XRCID("JoypadAutofireR"), UserInput('4', wxMOD_ALT)},
|
||||
{XRCID("VideoLayersBG0"), UserInput('1', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersBG1"), UserInput('2', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersBG2"), UserInput('3', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersBG3"), UserInput('4', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersOBJ"), UserInput('5', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersWIN0"), UserInput('6', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersWIN1"), UserInput('7', wxMOD_CMD)},
|
||||
{XRCID("VideoLayersOBJWIN"), UserInput('8', wxMOD_CMD)},
|
||||
{XRCID("Rewind"), UserInput('B', wxMOD_CMD)},
|
||||
// following are not in standard menus
|
||||
// FILExx are filled in when recent menu is filled
|
||||
{wxID_FILE1, UserInput(WXK_F1, wxMOD_CMD)},
|
||||
{wxID_FILE2, UserInput(WXK_F2, wxMOD_CMD)},
|
||||
{wxID_FILE3, UserInput(WXK_F3, wxMOD_CMD)},
|
||||
{wxID_FILE4, UserInput(WXK_F4, wxMOD_CMD)},
|
||||
{wxID_FILE5, UserInput(WXK_F5, wxMOD_CMD)},
|
||||
{wxID_FILE6, UserInput(WXK_F6, wxMOD_CMD)},
|
||||
{wxID_FILE7, UserInput(WXK_F7, wxMOD_CMD)},
|
||||
{wxID_FILE8, UserInput(WXK_F8, wxMOD_CMD)},
|
||||
{wxID_FILE9, UserInput(WXK_F9, wxMOD_CMD)},
|
||||
{wxID_FILE10, UserInput(WXK_F10, wxMOD_CMD)},
|
||||
{XRCID("VideoLayersReset"), UserInput('0', wxMOD_CMD)},
|
||||
{XRCID("ChangeFilter"), UserInput('G', wxMOD_CMD)},
|
||||
{XRCID("ChangeIFB"), UserInput('I', wxMOD_CMD)},
|
||||
{XRCID("IncreaseVolume"), UserInput(WXK_NUMPAD_ADD, wxMOD_NONE)},
|
||||
{XRCID("DecreaseVolume"), UserInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE)},
|
||||
{XRCID("ToggleSound"), UserInput(WXK_NUMPAD_ENTER, wxMOD_NONE)},
|
||||
};
|
||||
return kDefaultShortcuts;
|
||||
}
|
||||
|
||||
UserInput DefaultShortcutForCommand(int command) {
|
||||
const auto& iter = DefaultShortcuts().find(command);
|
||||
if (iter != DefaultShortcuts().end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return UserInput();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace config
|
@@ -1,21 +0,0 @@
|
||||
#ifndef VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
#error "Do not include "config/internal/shortcuts-internal.h" outside of the implementation."
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace config {
|
||||
namespace internal {
|
||||
|
||||
// Returns the map of commands to their default shortcut.
|
||||
const std::unordered_map<int, UserInput>& DefaultShortcuts();
|
||||
|
||||
// Returns the default shortcut for the given `command`.
|
||||
// Returns an Invalid UserInput if there is no default shortcut for `command`.
|
||||
UserInput DefaultShortcutForCommand(int command);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace config
|
@@ -99,6 +99,7 @@ enum class OptionID {
|
||||
kPrefSpeedupThrottle,
|
||||
kPrefSpeedupFrameSkip,
|
||||
kPrefSpeedupThrottleFrameSkip,
|
||||
kPrefSpeedupMute,
|
||||
kPrefUseBiosGB,
|
||||
kPrefUseBiosGBA,
|
||||
kPrefUseBiosGBC,
|
||||
|
@@ -1,7 +1,10 @@
|
||||
#include "wx/config/option-observer.h"
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "wx/config/option.h"
|
||||
|
||||
#include "core/base/check.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
// An Option::Observer that calls a callback when an option has changed.
|
||||
@@ -10,7 +13,7 @@ public:
|
||||
CallbackOptionObserver(const OptionID& option_id,
|
||||
std::function<void(Option*)> callback)
|
||||
: Option::Observer(option_id), callback_(std::move(callback)) {
|
||||
assert(callback_);
|
||||
VBAM_CHECK(callback_);
|
||||
}
|
||||
~CallbackOptionObserver() override = default;
|
||||
|
||||
|
@@ -103,6 +103,7 @@ static constexpr std::array<Option::Type, kNbOptions> kOptionsTypes = {
|
||||
/*kPrefSpeedupThrottle*/ Option::Type::kUnsigned,
|
||||
/*kPrefSpeedupFrameSkip*/ Option::Type::kUnsigned,
|
||||
/*kPrefSpeedupThrottleFrameSkip*/ Option::Type::kBool,
|
||||
/*kPrefSpeedupMute*/ Option::Type::kBool,
|
||||
/*kPrefUseBiosGB*/ Option::Type::kBool,
|
||||
/*kPrefUseBiosGBA*/ Option::Type::kBool,
|
||||
/*kPrefUseBiosGBC*/ Option::Type::kBool,
|
||||
@@ -174,11 +175,43 @@ private:
|
||||
Option* option_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class OptionProxyNumeric {
|
||||
public:
|
||||
virtual T Get() const = 0;
|
||||
virtual bool Set(T value) = 0;
|
||||
virtual T Min() const = 0;
|
||||
virtual T Max() const = 0;
|
||||
|
||||
bool operator++() { return *this += 1; }
|
||||
bool operator--() { return *this -= 1; }
|
||||
bool operator++(int) { return *this += 1; }
|
||||
bool operator--(int) { return *this -= 1; }
|
||||
bool operator+=(T value) {
|
||||
const T new_value = Get() + value;
|
||||
if (new_value > Max()) {
|
||||
return Set(Max());
|
||||
} else {
|
||||
return Set(new_value);
|
||||
}
|
||||
}
|
||||
bool operator-=(T value) {
|
||||
const T new_value = Get() - value;
|
||||
if (new_value < Min()) {
|
||||
return Set(Min());
|
||||
} else {
|
||||
return Set(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const { return Get(); }
|
||||
};
|
||||
|
||||
template <OptionID ID>
|
||||
class OptionProxy<
|
||||
ID,
|
||||
typename std::enable_if<kOptionsTypes[static_cast<size_t>(ID)] ==
|
||||
Option::Type::kDouble>::type> {
|
||||
Option::Type::kDouble>::type> : public OptionProxyNumeric<double> {
|
||||
public:
|
||||
OptionProxy() : option_(Option::ByID(ID)) {}
|
||||
~OptionProxy() = default;
|
||||
@@ -187,9 +220,7 @@ public:
|
||||
bool Set(double value) { return option_->SetDouble(value); }
|
||||
double Min() const { return option_->GetDoubleMin(); }
|
||||
double Max() const { return option_->GetDoubleMax(); }
|
||||
|
||||
bool operator=(double value) { return Set(value); }
|
||||
operator double() const { return Get(); }
|
||||
|
||||
private:
|
||||
Option* option_;
|
||||
@@ -199,7 +230,7 @@ template <OptionID ID>
|
||||
class OptionProxy<
|
||||
ID,
|
||||
typename std::enable_if<kOptionsTypes[static_cast<size_t>(ID)] ==
|
||||
Option::Type::kInt>::type> {
|
||||
Option::Type::kInt>::type> : public OptionProxyNumeric<int32_t> {
|
||||
public:
|
||||
OptionProxy() : option_(Option::ByID(ID)) {}
|
||||
~OptionProxy() = default;
|
||||
@@ -208,25 +239,7 @@ public:
|
||||
bool Set(int32_t value) { return option_->SetInt(value); }
|
||||
int32_t Min() const { return option_->GetIntMin(); }
|
||||
int32_t Max() const { return option_->GetIntMax(); }
|
||||
|
||||
bool operator=(int32_t value) { return Set(value); }
|
||||
bool operator+=(int32_t value) {
|
||||
const int new_value = Get() + value;
|
||||
if (new_value > Max()) {
|
||||
return Set(Max());
|
||||
} else {
|
||||
return Set(new_value);
|
||||
}
|
||||
}
|
||||
bool operator-=(int32_t value) {
|
||||
const int new_value = Get() - value;
|
||||
if (new_value < Min()) {
|
||||
return Set(Min());
|
||||
} else {
|
||||
return Set(new_value);
|
||||
}
|
||||
}
|
||||
operator int32_t() const { return Get(); }
|
||||
|
||||
private:
|
||||
Option* option_;
|
||||
@@ -236,7 +249,7 @@ template <OptionID ID>
|
||||
class OptionProxy<
|
||||
ID,
|
||||
typename std::enable_if<kOptionsTypes[static_cast<size_t>(ID)] ==
|
||||
Option::Type::kUnsigned>::type> {
|
||||
Option::Type::kUnsigned>::type> : public OptionProxyNumeric<uint32_t> {
|
||||
public:
|
||||
OptionProxy() : option_(Option::ByID(ID)) {}
|
||||
~OptionProxy() = default;
|
||||
@@ -245,9 +258,7 @@ public:
|
||||
bool Set(uint32_t value) { return option_->SetUnsigned(value); }
|
||||
uint32_t Min() const { return option_->GetUnsignedMin(); }
|
||||
uint32_t Max() const { return option_->GetUnsignedMax(); }
|
||||
|
||||
bool operator=(int32_t value) { return Set(value); }
|
||||
operator int32_t() const { return Get(); }
|
||||
bool operator=(uint32_t value) { return Set(value); }
|
||||
|
||||
private:
|
||||
Option* option_;
|
||||
|
461
src/wx/config/option-test.cpp
Normal file
461
src/wx/config/option-test.cpp
Normal file
@@ -0,0 +1,461 @@
|
||||
#include "wx/config/option.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#include "wx/config/option-id.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
|
||||
// Basic tests for a boolean option.
|
||||
TEST(OptionTest, Bool) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispBilinear);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_EQ(option->command(), "Bilinear");
|
||||
EXPECT_EQ(option->config_name(), "Display/Bilinear");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kDispBilinear);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kBool);
|
||||
EXPECT_TRUE(option->is_bool());
|
||||
|
||||
EXPECT_TRUE(option->SetBool(false));
|
||||
EXPECT_FALSE(option->GetBool());
|
||||
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Double) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispScale);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Display/Scale");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kDispScale);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kDouble);
|
||||
EXPECT_TRUE(option->is_double());
|
||||
|
||||
EXPECT_TRUE(option->SetDouble(2.5));
|
||||
EXPECT_DOUBLE_EQ(option->GetDouble(), 2.5);
|
||||
|
||||
// Need to disable logging to test for errors.
|
||||
const wxLogNull disable_logging;
|
||||
|
||||
// Test out of bounds values.
|
||||
EXPECT_FALSE(option->SetDouble(-1.0));
|
||||
EXPECT_FALSE(option->SetDouble(7.0));
|
||||
EXPECT_DOUBLE_EQ(option->GetDouble(), 2.5);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Int) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kSoundBuffers);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Sound/Buffers");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kSoundBuffers);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kInt);
|
||||
EXPECT_TRUE(option->is_int());
|
||||
|
||||
EXPECT_TRUE(option->SetInt(8));
|
||||
EXPECT_EQ(option->GetInt(), 8);
|
||||
|
||||
// Need to disable logging to test for errors.
|
||||
const wxLogNull disable_logging;
|
||||
|
||||
// Test out of bounds values.
|
||||
EXPECT_FALSE(option->SetInt(-1));
|
||||
EXPECT_FALSE(option->SetInt(42));
|
||||
EXPECT_EQ(option->GetInt(), 8);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Unsigned) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kGeomWindowHeight);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_EQ(option->config_name(), "geometry/windowHeight");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kGeomWindowHeight);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kUnsigned);
|
||||
EXPECT_TRUE(option->is_unsigned());
|
||||
|
||||
EXPECT_TRUE(option->SetUnsigned(100));
|
||||
EXPECT_EQ(option->GetUnsigned(), 100);
|
||||
|
||||
// Need to disable logging to test for errors.
|
||||
const wxLogNull disable_logging;
|
||||
|
||||
// Test out of bounds values.
|
||||
EXPECT_FALSE(option->SetUnsigned(100000));
|
||||
EXPECT_EQ(option->GetUnsigned(), 100);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, String) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kGenStateDir);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "General/StateDir");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kGenStateDir);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kString);
|
||||
EXPECT_TRUE(option->is_string());
|
||||
|
||||
EXPECT_TRUE(option->SetString("/path/to/sthg"));
|
||||
EXPECT_EQ(option->GetString(), "/path/to/sthg");
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Filter) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispFilter);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Display/Filter");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kDispFilter);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kFilter);
|
||||
EXPECT_TRUE(option->is_filter());
|
||||
|
||||
EXPECT_TRUE(option->SetFilter(config::Filter::kNone));
|
||||
EXPECT_EQ(option->GetFilter(), config::Filter::kNone);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Interframe) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispIFB);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Display/IFB");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kDispIFB);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kInterframe);
|
||||
EXPECT_TRUE(option->is_interframe());
|
||||
|
||||
EXPECT_TRUE(option->SetInterframe(config::Interframe::kNone));
|
||||
EXPECT_EQ(option->GetInterframe(), config::Interframe::kNone);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, AudioApi) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kSoundAudioAPI);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Sound/AudioAPI");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kSoundAudioAPI);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kAudioApi);
|
||||
EXPECT_TRUE(option->is_audio_api());
|
||||
|
||||
EXPECT_TRUE(option->SetAudioApi(config::AudioApi::kOpenAL));
|
||||
EXPECT_EQ(option->GetAudioApi(), config::AudioApi::kOpenAL);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, AudioRate) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kSoundAudioRate);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Sound/Quality");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kSoundAudioRate);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kAudioRate);
|
||||
EXPECT_TRUE(option->is_audio_rate());
|
||||
|
||||
EXPECT_TRUE(option->SetAudioRate(config::AudioRate::k11kHz));
|
||||
EXPECT_EQ(option->GetAudioRate(), config::AudioRate::k11kHz);
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetGbPalette({0, 1, 2, 3, 4, 5, 6, 7}), "is_gb_palette\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionTest, Enum) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispRenderMethod);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "Display/RenderMethod");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kDispRenderMethod);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kRenderMethod);
|
||||
EXPECT_TRUE(option->is_render_method());
|
||||
|
||||
EXPECT_TRUE(option->SetRenderMethod(config::RenderMethod::kSimple));
|
||||
EXPECT_EQ(option->GetRenderMethod(), config::RenderMethod::kSimple);
|
||||
|
||||
EXPECT_TRUE(option->SetEnumString("opengl"));
|
||||
EXPECT_EQ(option->GetRenderMethod(), config::RenderMethod::kOpenGL);
|
||||
}
|
||||
|
||||
TEST(Optiontest, GbPalette) {
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kGBPalette0);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_TRUE(option->command().empty());
|
||||
EXPECT_EQ(option->config_name(), "GB/Palette0");
|
||||
EXPECT_EQ(option->id(), config::OptionID::kGBPalette0);
|
||||
EXPECT_EQ(option->type(), config::Option::Type::kGbPalette);
|
||||
EXPECT_TRUE(option->is_gb_palette());
|
||||
|
||||
const std::array<uint16_t, 8> palette = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
EXPECT_TRUE(option->SetGbPalette(palette));
|
||||
EXPECT_EQ(option->GetGbPalette(), palette);
|
||||
|
||||
EXPECT_EQ(option->GetGbPaletteString(), "0000,0001,0002,0003,0004,0005,0006,0007");
|
||||
|
||||
// Need to disable logging to test for errors.
|
||||
const wxLogNull disable_logging;
|
||||
|
||||
// Incorrect configuration string tests
|
||||
EXPECT_FALSE(option->SetGbPaletteString(""));
|
||||
EXPECT_FALSE(option->SetGbPaletteString("0000,0001,0002,0003,0004,0005,0006,000Q"));
|
||||
|
||||
EXPECT_DEATH(option->SetBool(true), "is_bool\\(\\)");
|
||||
EXPECT_DEATH(option->SetDouble(2.0), "is_double\\(\\)");
|
||||
EXPECT_DEATH(option->SetInt(2), "is_int\\(\\)");
|
||||
EXPECT_DEATH(option->SetUnsigned(2), "is_unsigned\\(\\)");
|
||||
EXPECT_DEATH(option->SetString("foo"), "is_string\\(\\)");
|
||||
EXPECT_DEATH(option->SetFilter(config::Filter::kNone), "is_filter\\(\\)");
|
||||
EXPECT_DEATH(option->SetInterframe(config::Interframe::kNone), "is_interframe\\(\\)");
|
||||
EXPECT_DEATH(option->SetRenderMethod(config::RenderMethod::kSimple), "is_render_method\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioApi(config::AudioApi::kOpenAL), "is_audio_api\\(\\)");
|
||||
EXPECT_DEATH(option->SetAudioRate(config::AudioRate::k11kHz), "is_audio_rate\\(\\)");
|
||||
}
|
||||
|
||||
TEST(OptionObserverTest, Basic) {
|
||||
class TestOptionsObserver : public config::Option::Observer {
|
||||
public:
|
||||
TestOptionsObserver(config::OptionID id) : config::Option::Observer(id), changed_(false) {}
|
||||
~TestOptionsObserver() override = default;
|
||||
|
||||
void OnValueChanged() override { changed_ = true; }
|
||||
|
||||
bool changed() const { return changed_; }
|
||||
void ResetChanged() { changed_ = false; }
|
||||
|
||||
private:
|
||||
bool changed_;
|
||||
};
|
||||
|
||||
config::Option* option = config::Option::ByID(config::OptionID::kDispKeepOnTop);
|
||||
ASSERT_TRUE(option);
|
||||
const bool initial_value = option->GetBool();
|
||||
|
||||
TestOptionsObserver observer(config::OptionID::kDispKeepOnTop);
|
||||
|
||||
// Test that the observer is not notified when the value does not change.
|
||||
EXPECT_TRUE(option->SetBool(initial_value));
|
||||
EXPECT_FALSE(observer.changed());
|
||||
observer.ResetChanged();
|
||||
|
||||
// Test that the observer is notified when the value changes.
|
||||
EXPECT_TRUE(option->SetBool(!initial_value));
|
||||
EXPECT_TRUE(observer.changed());
|
||||
observer.ResetChanged();
|
||||
|
||||
EXPECT_TRUE(option->SetBool(initial_value));
|
||||
EXPECT_TRUE(observer.changed());
|
||||
}
|
||||
|
||||
// Tests that the option proxy correctly matches the types of the options.
|
||||
TEST(OptionProxyTest, MatchingTypes) {
|
||||
for (size_t i = 0; i < config::kNbOptions; i++) {
|
||||
const config::OptionID id = static_cast<config::OptionID>(i);
|
||||
const config::Option::Type proxy_type = config::kOptionsTypes[i];
|
||||
const config::Option* option = config::Option::ByID(id);
|
||||
|
||||
ASSERT_TRUE(option);
|
||||
EXPECT_EQ(option->type(), proxy_type);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OptionProxyTest, NumericOperators) {
|
||||
wxLogNull disable_logging;
|
||||
|
||||
int32_t int_opt = OPTION(kDispMaxThreads);
|
||||
|
||||
int_opt++;
|
||||
OPTION(kDispMaxThreads)++;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
int_opt--;
|
||||
OPTION(kDispMaxThreads)--;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
++int_opt;
|
||||
OPTION(kDispMaxThreads)++;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
--int_opt;
|
||||
OPTION(kDispMaxThreads)--;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
int_opt += 2;
|
||||
OPTION(kDispMaxThreads) += 2;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
int_opt -= 2;
|
||||
OPTION(kDispMaxThreads) -= 2;
|
||||
EXPECT_EQ(int_opt, OPTION(kDispMaxThreads));
|
||||
|
||||
OPTION(kDispMaxThreads) = OPTION(kDispMaxThreads).Max();
|
||||
OPTION(kDispMaxThreads)++;
|
||||
EXPECT_EQ(OPTION(kDispMaxThreads), OPTION(kDispMaxThreads).Max());
|
||||
|
||||
OPTION(kDispMaxThreads) = OPTION(kDispMaxThreads).Min();
|
||||
OPTION(kDispMaxThreads)--;
|
||||
EXPECT_EQ(OPTION(kDispMaxThreads), OPTION(kDispMaxThreads).Min());
|
||||
|
||||
uint32_t unsigned_opt = OPTION(kJoyDefault);
|
||||
|
||||
unsigned_opt++;
|
||||
OPTION(kJoyDefault)++;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
unsigned_opt--;
|
||||
OPTION(kJoyDefault)--;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
++unsigned_opt;
|
||||
OPTION(kJoyDefault)++;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
--unsigned_opt;
|
||||
OPTION(kJoyDefault)--;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
unsigned_opt += 2;
|
||||
OPTION(kJoyDefault) += 2;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
unsigned_opt -= 2;
|
||||
OPTION(kJoyDefault) -= 2;
|
||||
EXPECT_EQ(unsigned_opt, OPTION(kJoyDefault));
|
||||
|
||||
OPTION(kJoyDefault) = OPTION(kJoyDefault).Max();
|
||||
OPTION(kJoyDefault)++;
|
||||
EXPECT_EQ(OPTION(kJoyDefault), OPTION(kJoyDefault).Max());
|
||||
|
||||
OPTION(kJoyDefault) = OPTION(kJoyDefault).Min();
|
||||
OPTION(kJoyDefault)--;
|
||||
EXPECT_EQ(OPTION(kJoyDefault), OPTION(kJoyDefault).Min());
|
||||
|
||||
double double_opt = OPTION(kDispScale);
|
||||
|
||||
double_opt++;
|
||||
OPTION(kDispScale)++;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
double_opt--;
|
||||
OPTION(kDispScale)--;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
++double_opt;
|
||||
OPTION(kDispScale)++;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
--double_opt;
|
||||
OPTION(kDispScale)--;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
double_opt += 2;
|
||||
OPTION(kDispScale) += 2;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
double_opt -= 2;
|
||||
OPTION(kDispScale) -= 2;
|
||||
EXPECT_EQ(double_opt, OPTION(kDispScale));
|
||||
|
||||
OPTION(kDispScale) = OPTION(kDispScale).Max();
|
||||
OPTION(kDispScale)++;
|
||||
EXPECT_EQ(OPTION(kDispScale), OPTION(kDispScale).Max());
|
||||
|
||||
OPTION(kDispScale) = OPTION(kDispScale).Min();
|
||||
OPTION(kDispScale)--;
|
||||
EXPECT_EQ(OPTION(kDispScale), OPTION(kDispScale).Min());
|
||||
}
|
@@ -11,6 +11,7 @@
|
||||
#include "wx/config/internal/option-internal.h"
|
||||
#undef VBAM_OPTION_INTERNAL_INCLUDE
|
||||
|
||||
#include "core/base/check.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
|
||||
namespace config {
|
||||
@@ -26,14 +27,14 @@ Option* Option::ByName(const wxString& config_name) {
|
||||
|
||||
// static
|
||||
Option* Option::ByID(OptionID id) {
|
||||
assert(id != OptionID::Last);
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
return &All()[static_cast<size_t>(id)];
|
||||
}
|
||||
|
||||
Option::~Option() = default;
|
||||
|
||||
Option::Observer::Observer(OptionID option_id) : option_(Option::ByID(option_id)) {
|
||||
assert(option_);
|
||||
VBAM_CHECK(option_);
|
||||
option_->AddObserver(this);
|
||||
}
|
||||
Option::Observer::~Observer() {
|
||||
@@ -49,8 +50,8 @@ Option::Option(OptionID id)
|
||||
value_(),
|
||||
min_(),
|
||||
max_() {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_none());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_none());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, bool* option)
|
||||
@@ -62,8 +63,8 @@ Option::Option(OptionID id, bool* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_() {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_bool());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_bool());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, double* option, double min, double max)
|
||||
@@ -75,8 +76,8 @@ Option::Option(OptionID id, double* option, double min, double max)
|
||||
value_(option),
|
||||
min_(min),
|
||||
max_(max) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_double());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_double());
|
||||
|
||||
// Validate the initial value.
|
||||
SetDouble(*option);
|
||||
@@ -91,8 +92,8 @@ Option::Option(OptionID id, int32_t* option, int32_t min, int32_t max)
|
||||
value_(option),
|
||||
min_(min),
|
||||
max_(max) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_int());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_int());
|
||||
|
||||
// Validate the initial value.
|
||||
SetInt(*option);
|
||||
@@ -107,8 +108,8 @@ Option::Option(OptionID id, uint32_t* option, uint32_t min, uint32_t max)
|
||||
value_(option),
|
||||
min_(min),
|
||||
max_(max) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_unsigned());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_unsigned());
|
||||
|
||||
// Validate the initial value.
|
||||
SetUnsigned(*option);
|
||||
@@ -123,8 +124,8 @@ Option::Option(OptionID id, wxString* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_() {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_string());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_string());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, Filter* option)
|
||||
@@ -136,8 +137,8 @@ Option::Option(OptionID id, Filter* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_filter());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_filter());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, Interframe* option)
|
||||
@@ -149,8 +150,8 @@ Option::Option(OptionID id, Interframe* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_interframe());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_interframe());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, RenderMethod* option)
|
||||
@@ -162,8 +163,8 @@ Option::Option(OptionID id, RenderMethod* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_render_method());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_render_method());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, AudioApi* option)
|
||||
@@ -175,8 +176,8 @@ Option::Option(OptionID id, AudioApi* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_audio_api());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_audio_api());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, AudioRate* option)
|
||||
@@ -188,8 +189,8 @@ Option::Option(OptionID id, AudioRate* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_(nonstd::in_place_type<size_t>, internal::MaxForType(type_)) {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_audio_rate());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_audio_rate());
|
||||
}
|
||||
|
||||
Option::Option(OptionID id, uint16_t* option)
|
||||
@@ -201,57 +202,57 @@ Option::Option(OptionID id, uint16_t* option)
|
||||
value_(option),
|
||||
min_(),
|
||||
max_() {
|
||||
assert(id != OptionID::Last);
|
||||
assert(is_gb_palette());
|
||||
VBAM_CHECK(id != OptionID::Last);
|
||||
VBAM_CHECK(is_gb_palette());
|
||||
}
|
||||
|
||||
bool Option::GetBool() const {
|
||||
assert(is_bool());
|
||||
VBAM_CHECK(is_bool());
|
||||
return *(nonstd::get<bool*>(value_));
|
||||
}
|
||||
|
||||
double Option::GetDouble() const {
|
||||
assert(is_double());
|
||||
VBAM_CHECK(is_double());
|
||||
return *(nonstd::get<double*>(value_));
|
||||
}
|
||||
|
||||
int32_t Option::GetInt() const {
|
||||
assert(is_int());
|
||||
VBAM_CHECK(is_int());
|
||||
return *(nonstd::get<int32_t*>(value_));
|
||||
}
|
||||
|
||||
uint32_t Option::GetUnsigned() const {
|
||||
assert(is_unsigned());
|
||||
VBAM_CHECK(is_unsigned());
|
||||
return *(nonstd::get<uint32_t*>(value_));
|
||||
}
|
||||
|
||||
const wxString& Option::GetString() const {
|
||||
assert(is_string());
|
||||
VBAM_CHECK(is_string());
|
||||
return *(nonstd::get<wxString*>(value_));
|
||||
}
|
||||
|
||||
Filter Option::GetFilter() const {
|
||||
assert(is_filter());
|
||||
VBAM_CHECK(is_filter());
|
||||
return *(nonstd::get<Filter*>(value_));
|
||||
}
|
||||
|
||||
Interframe Option::GetInterframe() const {
|
||||
assert(is_interframe());
|
||||
VBAM_CHECK(is_interframe());
|
||||
return *(nonstd::get<Interframe*>(value_));
|
||||
}
|
||||
|
||||
RenderMethod Option::GetRenderMethod() const {
|
||||
assert(is_render_method());
|
||||
VBAM_CHECK(is_render_method());
|
||||
return *(nonstd::get<RenderMethod*>(value_));
|
||||
}
|
||||
|
||||
AudioApi Option::GetAudioApi() const {
|
||||
assert(is_audio_api());
|
||||
VBAM_CHECK(is_audio_api());
|
||||
return *(nonstd::get<AudioApi*>(value_));
|
||||
}
|
||||
|
||||
AudioRate Option::GetAudioRate() const {
|
||||
assert(is_audio_rate());
|
||||
VBAM_CHECK(is_audio_rate());
|
||||
return *(nonstd::get<AudioRate*>(value_));
|
||||
}
|
||||
|
||||
@@ -277,15 +278,15 @@ wxString Option::GetEnumString() const {
|
||||
case Option::Type::kUnsigned:
|
||||
case Option::Type::kString:
|
||||
case Option::Type::kGbPalette:
|
||||
assert(false);
|
||||
VBAM_CHECK(false);
|
||||
return wxEmptyString;
|
||||
}
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
std::array<uint16_t, 8> Option::GetGbPalette() const {
|
||||
assert(is_gb_palette());
|
||||
VBAM_CHECK(is_gb_palette());
|
||||
|
||||
const uint16_t* raw_palette = (nonstd::get<uint16_t*>(value_));
|
||||
std::array<uint16_t, 8> palette;
|
||||
@@ -294,7 +295,7 @@ std::array<uint16_t, 8> Option::GetGbPalette() const {
|
||||
}
|
||||
|
||||
wxString Option::GetGbPaletteString() const {
|
||||
assert(is_gb_palette());
|
||||
VBAM_CHECK(is_gb_palette());
|
||||
|
||||
wxString palette_string;
|
||||
uint16_t const* value = nonstd::get<uint16_t*>(value_);
|
||||
@@ -304,7 +305,7 @@ wxString Option::GetGbPaletteString() const {
|
||||
}
|
||||
|
||||
bool Option::SetBool(bool value) {
|
||||
assert(is_bool());
|
||||
VBAM_CHECK(is_bool());
|
||||
bool old_value = GetBool();
|
||||
*nonstd::get<bool*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -314,7 +315,7 @@ bool Option::SetBool(bool value) {
|
||||
}
|
||||
|
||||
bool Option::SetDouble(double value) {
|
||||
assert(is_double());
|
||||
VBAM_CHECK(is_double());
|
||||
double old_value = GetDouble();
|
||||
if (value < nonstd::get<double>(min_) || value > nonstd::get<double>(max_)) {
|
||||
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), value,
|
||||
@@ -329,7 +330,7 @@ bool Option::SetDouble(double value) {
|
||||
}
|
||||
|
||||
bool Option::SetInt(int32_t value) {
|
||||
assert(is_int());
|
||||
VBAM_CHECK(is_int());
|
||||
int old_value = GetInt();
|
||||
if (value < nonstd::get<int32_t>(min_) || value > nonstd::get<int32_t>(max_)) {
|
||||
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), value,
|
||||
@@ -344,7 +345,7 @@ bool Option::SetInt(int32_t value) {
|
||||
}
|
||||
|
||||
bool Option::SetUnsigned(uint32_t value) {
|
||||
assert(is_unsigned());
|
||||
VBAM_CHECK(is_unsigned());
|
||||
uint32_t old_value = GetUnsigned();
|
||||
if (value < nonstd::get<uint32_t>(min_) || value > nonstd::get<uint32_t>(max_)) {
|
||||
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), value,
|
||||
@@ -359,7 +360,7 @@ bool Option::SetUnsigned(uint32_t value) {
|
||||
}
|
||||
|
||||
bool Option::SetString(const wxString& value) {
|
||||
assert(is_string());
|
||||
VBAM_CHECK(is_string());
|
||||
const wxString old_value = GetString();
|
||||
*nonstd::get<wxString*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -369,8 +370,8 @@ bool Option::SetString(const wxString& value) {
|
||||
}
|
||||
|
||||
bool Option::SetFilter(const Filter& value) {
|
||||
assert(is_filter());
|
||||
assert(value < Filter::kLast);
|
||||
VBAM_CHECK(is_filter());
|
||||
VBAM_CHECK(value < Filter::kLast);
|
||||
const Filter old_value = GetFilter();
|
||||
*nonstd::get<Filter*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -380,8 +381,8 @@ bool Option::SetFilter(const Filter& value) {
|
||||
}
|
||||
|
||||
bool Option::SetInterframe(const Interframe& value) {
|
||||
assert(is_interframe());
|
||||
assert(value < Interframe::kLast);
|
||||
VBAM_CHECK(is_interframe());
|
||||
VBAM_CHECK(value < Interframe::kLast);
|
||||
const Interframe old_value = GetInterframe();
|
||||
*nonstd::get<Interframe*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -391,8 +392,8 @@ bool Option::SetInterframe(const Interframe& value) {
|
||||
}
|
||||
|
||||
bool Option::SetRenderMethod(const RenderMethod& value) {
|
||||
assert(is_render_method());
|
||||
assert(value < RenderMethod::kLast);
|
||||
VBAM_CHECK(is_render_method());
|
||||
VBAM_CHECK(value < RenderMethod::kLast);
|
||||
const RenderMethod old_value = GetRenderMethod();
|
||||
*nonstd::get<RenderMethod*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -402,8 +403,8 @@ bool Option::SetRenderMethod(const RenderMethod& value) {
|
||||
}
|
||||
|
||||
bool Option::SetAudioApi(const AudioApi& value) {
|
||||
assert(is_audio_api());
|
||||
assert(value < AudioApi::kLast);
|
||||
VBAM_CHECK(is_audio_api());
|
||||
VBAM_CHECK(value < AudioApi::kLast);
|
||||
const AudioApi old_value = GetAudioApi();
|
||||
*nonstd::get<AudioApi*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -413,8 +414,8 @@ bool Option::SetAudioApi(const AudioApi& value) {
|
||||
}
|
||||
|
||||
bool Option::SetAudioRate(const AudioRate& value) {
|
||||
assert(is_audio_rate());
|
||||
assert(value < AudioRate::kLast);
|
||||
VBAM_CHECK(is_audio_rate());
|
||||
VBAM_CHECK(value < AudioRate::kLast);
|
||||
const AudioRate old_value = GetAudioRate();
|
||||
*nonstd::get<AudioRate*>(value_) = value;
|
||||
if (old_value != value) {
|
||||
@@ -445,15 +446,15 @@ bool Option::SetEnumString(const wxString& value) {
|
||||
case Option::Type::kUnsigned:
|
||||
case Option::Type::kString:
|
||||
case Option::Type::kGbPalette:
|
||||
assert(false);
|
||||
VBAM_CHECK(false);
|
||||
return false;
|
||||
}
|
||||
assert(false);
|
||||
VBAM_NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Option::SetGbPalette(const std::array<uint16_t, 8>& value) {
|
||||
assert(is_gb_palette());
|
||||
VBAM_CHECK(is_gb_palette());
|
||||
|
||||
uint16_t* dest = nonstd::get<uint16_t*>(value_);
|
||||
|
||||
@@ -471,7 +472,7 @@ bool Option::SetGbPalette(const std::array<uint16_t, 8>& value) {
|
||||
}
|
||||
|
||||
bool Option::SetGbPaletteString(const wxString& value) {
|
||||
assert(is_gb_palette());
|
||||
VBAM_CHECK(is_gb_palette());
|
||||
|
||||
// 8 values of 4 chars and 7 commas.
|
||||
static constexpr size_t kPaletteStringSize = 8 * 4 + 7;
|
||||
@@ -487,6 +488,9 @@ bool Option::SetGbPaletteString(const wxString& value) {
|
||||
long temp = 0;
|
||||
if (number.ToLong(&temp, 16)) {
|
||||
new_value[i] = temp;
|
||||
} else {
|
||||
wxLogWarning(_("Invalid value %s for option %s"), value, config_name_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,50 +498,50 @@ bool Option::SetGbPaletteString(const wxString& value) {
|
||||
}
|
||||
|
||||
double Option::GetDoubleMin() const {
|
||||
assert(is_double());
|
||||
VBAM_CHECK(is_double());
|
||||
return nonstd::get<double>(min_);
|
||||
}
|
||||
|
||||
double Option::GetDoubleMax() const {
|
||||
assert(is_double());
|
||||
VBAM_CHECK(is_double());
|
||||
return nonstd::get<double>(max_);
|
||||
}
|
||||
|
||||
int32_t Option::GetIntMin() const {
|
||||
assert(is_int());
|
||||
VBAM_CHECK(is_int());
|
||||
return nonstd::get<int32_t>(min_);
|
||||
}
|
||||
|
||||
int32_t Option::GetIntMax() const {
|
||||
assert(is_int());
|
||||
VBAM_CHECK(is_int());
|
||||
return nonstd::get<int32_t>(max_);
|
||||
}
|
||||
|
||||
uint32_t Option::GetUnsignedMin() const {
|
||||
assert(is_unsigned());
|
||||
VBAM_CHECK(is_unsigned());
|
||||
return nonstd::get<uint32_t>(min_);
|
||||
}
|
||||
|
||||
uint32_t Option::GetUnsignedMax() const {
|
||||
assert(is_unsigned());
|
||||
VBAM_CHECK(is_unsigned());
|
||||
return nonstd::get<uint32_t>(max_);
|
||||
}
|
||||
|
||||
size_t Option::GetEnumMax() const {
|
||||
assert(is_filter() || is_interframe() || is_render_method() || is_audio_api() ||
|
||||
VBAM_CHECK(is_filter() || is_interframe() || is_render_method() || is_audio_api() ||
|
||||
is_audio_rate());
|
||||
return nonstd::get<size_t>(max_);
|
||||
}
|
||||
|
||||
void Option::NextFilter() {
|
||||
assert(is_filter());
|
||||
VBAM_CHECK(is_filter());
|
||||
const int old_value = static_cast<int>(GetFilter());
|
||||
const int new_value = (old_value + 1) % kNbFilters;
|
||||
SetFilter(static_cast<Filter>(new_value));
|
||||
}
|
||||
|
||||
void Option::NextInterframe() {
|
||||
assert(is_interframe());
|
||||
VBAM_CHECK(is_interframe());
|
||||
const int old_value = static_cast<int>(GetInterframe());
|
||||
const int new_value = (old_value + 1) % kNbInterframes;
|
||||
SetInterframe(static_cast<Interframe>(new_value));
|
||||
@@ -585,19 +589,19 @@ wxString Option::ToHelperString() const {
|
||||
}
|
||||
|
||||
void Option::AddObserver(Observer* observer) {
|
||||
assert(observer);
|
||||
VBAM_CHECK(observer);
|
||||
[[maybe_unused]] const auto pair = observers_.emplace(observer);
|
||||
assert(pair.second);
|
||||
VBAM_CHECK(pair.second);
|
||||
}
|
||||
|
||||
void Option::RemoveObserver(Observer* observer) {
|
||||
assert(observer);
|
||||
VBAM_CHECK(observer);
|
||||
[[maybe_unused]] const size_t removed = observers_.erase(observer);
|
||||
assert(removed == 1u);
|
||||
VBAM_CHECK(removed == 1u);
|
||||
}
|
||||
|
||||
void Option::CallObservers() {
|
||||
assert(!calling_observers_);
|
||||
VBAM_CHECK(!calling_observers_);
|
||||
calling_observers_ = true;
|
||||
for (const auto observer : observers_) {
|
||||
observer->OnValueChanged();
|
||||
|
@@ -1,183 +0,0 @@
|
||||
#include "wx/config/shortcuts.h"
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
#define VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
#include "wx/config/internal/shortcuts-internal.h"
|
||||
#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE
|
||||
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
int NoopCommand() {
|
||||
static const int noop = XRCID("NOOP");
|
||||
return noop;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Shortcuts::Shortcuts() {
|
||||
// Set up default shortcuts.
|
||||
for (const auto& iter : internal::DefaultShortcuts()) {
|
||||
AssignInputToCommand(iter.second, iter.first);
|
||||
}
|
||||
}
|
||||
|
||||
Shortcuts::Shortcuts(const std::unordered_map<int, std::set<UserInput>>& command_to_inputs,
|
||||
const std::map<UserInput, int>& input_to_command,
|
||||
const std::map<UserInput, int>& disabled_defaults)
|
||||
: command_to_inputs_(command_to_inputs.begin(), command_to_inputs.end()),
|
||||
input_to_command_(input_to_command.begin(), input_to_command.end()),
|
||||
disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {}
|
||||
|
||||
std::vector<std::pair<int, wxString>> Shortcuts::GetConfiguration() const {
|
||||
std::vector<std::pair<int, wxString>> config;
|
||||
config.reserve(command_to_inputs_.size() + 1);
|
||||
|
||||
if (!disabled_defaults_.empty()) {
|
||||
std::set<UserInput> noop_inputs;
|
||||
for (const auto& iter : disabled_defaults_) {
|
||||
noop_inputs.insert(iter.first);
|
||||
}
|
||||
config.push_back(std::make_pair(NoopCommand(), UserInput::SpanToConfigString(noop_inputs)));
|
||||
}
|
||||
|
||||
for (const auto& iter : command_to_inputs_) {
|
||||
std::set<UserInput> inputs;
|
||||
for (const auto& input : iter.second) {
|
||||
if (internal::DefaultShortcutForCommand(iter.first) != input) {
|
||||
// Not a default input.
|
||||
inputs.insert(input);
|
||||
}
|
||||
}
|
||||
if (!inputs.empty()) {
|
||||
config.push_back(std::make_pair(iter.first, UserInput::SpanToConfigString(inputs)));
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::set<UserInput> Shortcuts::InputsForCommand(int command) const {
|
||||
if (command == NoopCommand()) {
|
||||
std::set<UserInput> noop_inputs;
|
||||
for (const auto& iter : disabled_defaults_) {
|
||||
noop_inputs.insert(iter.first);
|
||||
}
|
||||
return noop_inputs;
|
||||
}
|
||||
|
||||
auto iter = command_to_inputs_.find(command);
|
||||
if (iter == command_to_inputs_.end()) {
|
||||
return {};
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int Shortcuts::CommandForInput(const UserInput& input) const {
|
||||
const auto iter = input_to_command_.find(input);
|
||||
if (iter == input_to_command_.end()) {
|
||||
return 0;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::set<wxJoystick> Shortcuts::Joysticks() const {
|
||||
std::set<wxJoystick> output;
|
||||
for (const auto& iter : command_to_inputs_) {
|
||||
for (const UserInput& user_input : iter.second) {
|
||||
output.insert(user_input.joystick());
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Shortcuts Shortcuts::Clone() const {
|
||||
return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_);
|
||||
}
|
||||
|
||||
void Shortcuts::AssignInputToCommand(const UserInput& input, int command) {
|
||||
if (command == NoopCommand()) {
|
||||
// "Assigning to Noop" means unassinging the default binding.
|
||||
UnassignDefaultBinding(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the existing binding if it exists.
|
||||
auto iter = input_to_command_.find(input);
|
||||
if (iter != input_to_command_.end()) {
|
||||
UnassignInput(input);
|
||||
}
|
||||
|
||||
auto disabled_iter = disabled_defaults_.find(input);
|
||||
if (disabled_iter != disabled_defaults_.end()) {
|
||||
int original_command = disabled_iter->second;
|
||||
if (original_command == command) {
|
||||
// Restoring a disabled input. Remove from the disabled set.
|
||||
disabled_defaults_.erase(disabled_iter);
|
||||
}
|
||||
// Then, just continue normally.
|
||||
}
|
||||
|
||||
command_to_inputs_[command].emplace(input);
|
||||
input_to_command_[input] = command;
|
||||
}
|
||||
|
||||
void Shortcuts::UnassignInput(const UserInput& input) {
|
||||
assert(input);
|
||||
|
||||
auto iter = input_to_command_.find(input);
|
||||
if (iter == input_to_command_.end()) {
|
||||
// Input not found, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (internal::DefaultShortcutForCommand(iter->second) == input) {
|
||||
// Unassigning a default binding has some special handling.
|
||||
UnassignDefaultBinding(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, just remove it from the 2 maps.
|
||||
auto command_iter = command_to_inputs_.find(iter->second);
|
||||
assert(command_iter != command_to_inputs_.end());
|
||||
|
||||
command_iter->second.erase(input);
|
||||
if (command_iter->second.empty()) {
|
||||
// Remove empty set.
|
||||
command_to_inputs_.erase(command_iter);
|
||||
}
|
||||
input_to_command_.erase(iter);
|
||||
}
|
||||
|
||||
void Shortcuts::UnassignDefaultBinding(const UserInput& input) {
|
||||
auto input_iter = input_to_command_.find(input);
|
||||
if (input_iter == input_to_command_.end()) {
|
||||
// This can happen if the INI file provided by the user has an invalid
|
||||
// option. In this case, just silently ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (internal::DefaultShortcutForCommand(input_iter->second) != input) {
|
||||
// As above, we have already removed the default binding, ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
auto command_iter = command_to_inputs_.find(input_iter->second);
|
||||
assert(command_iter != command_to_inputs_.end());
|
||||
|
||||
command_iter->second.erase(input);
|
||||
if (command_iter->second.empty()) {
|
||||
command_to_inputs_.erase(command_iter);
|
||||
}
|
||||
|
||||
disabled_defaults_[input] = input_iter->second;
|
||||
input_to_command_.erase(input_iter);
|
||||
}
|
||||
|
||||
} // namespace config
|
@@ -1,86 +0,0 @@
|
||||
#ifndef VBAM_WX_CONFIG_SHORTCUTS_H_
|
||||
#define VBAM_WX_CONFIG_SHORTCUTS_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
// by default, only 9 recent items
|
||||
#define wxID_FILE10 (wxID_FILE9 + 1)
|
||||
|
||||
namespace config {
|
||||
|
||||
// Represents a set of shortcuts from a user input (keyboard or joypad) to a
|
||||
// command. Internally, this class keeps track of all the necessary data for
|
||||
// resolving a user input to a command at runtime and for updating the INI file.
|
||||
class Shortcuts {
|
||||
public:
|
||||
Shortcuts();
|
||||
~Shortcuts() = default;
|
||||
|
||||
Shortcuts(Shortcuts&&) noexcept = default;
|
||||
Shortcuts& operator=(Shortcuts&&) noexcept = default;
|
||||
|
||||
// Disable copy and copy assignment operator.
|
||||
// Clone() is provided only for the configuration window, this class
|
||||
// should otherwise be treated as move-only.
|
||||
Shortcuts(const Shortcuts&) = delete;
|
||||
Shortcuts& operator=(const Shortcuts&) = delete;
|
||||
|
||||
// Returns the configuration for the INI file.
|
||||
// Internally, there are global default system inputs that are immediately
|
||||
// available on first run. For the configuration saved in the [Keyboard]
|
||||
// section of the vbam.ini file, we only keep track of the following:
|
||||
// - Disabled default input. These appear under [Keyboard/NOOP].
|
||||
// - User-added custom bindings. These appear under [Keyboard/CommandName].
|
||||
std::vector<std::pair<int, wxString>> GetConfiguration() const;
|
||||
|
||||
// Returns the list of input currently configured for `command`.
|
||||
std::set<UserInput> InputsForCommand(int command) const;
|
||||
|
||||
// Returns the command currently assigned to `input` or nullptr if none.
|
||||
int CommandForInput(const UserInput& input) const;
|
||||
|
||||
// Returns the set of joysticks used by shortcuts.
|
||||
std::set<wxJoystick> Joysticks() const;
|
||||
|
||||
// Returns a copy of this object. This can be an expensive operation and
|
||||
// should only be used to modify the currently active shortcuts
|
||||
// configuration.
|
||||
Shortcuts Clone() const;
|
||||
|
||||
// Assigns `input` to `command`. Silently unassigns `input` if it is already
|
||||
// assigned to another command.
|
||||
void AssignInputToCommand(const UserInput& input, int command);
|
||||
|
||||
// Removes `input` assignment. No-op if `input` is not assigned. `input`
|
||||
// must be a valid UserInput.
|
||||
void UnassignInput(const UserInput& input);
|
||||
|
||||
private:
|
||||
// Faster constructor for explitit copy.
|
||||
Shortcuts(const std::unordered_map<int, std::set<UserInput>>& command_to_inputs,
|
||||
const std::map<UserInput, int>& input_to_command,
|
||||
const std::map<UserInput, int>& disabled_defaults);
|
||||
|
||||
// Helper method to unassign a binding used by the default configuration.
|
||||
// This requires special handling since the INI configuration is a diff
|
||||
// between the default bindings and the user configuration.
|
||||
void UnassignDefaultBinding(const UserInput& input);
|
||||
|
||||
// Map of command to their associated input set.
|
||||
std::unordered_map<int, std::set<UserInput>> command_to_inputs_;
|
||||
// Reverse map of the above. An input can only map to a single command.
|
||||
std::map<UserInput, int> input_to_command_;
|
||||
// Disabled default inputs. This is used to easily retrieve the
|
||||
// configuration to save in the INI file.
|
||||
std::map<UserInput, int> disabled_defaults_;
|
||||
};
|
||||
|
||||
} // namespace config
|
||||
|
||||
#endif // VBAM_WX_CONFIG_SHORTCUTS_H_
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user