Compare commits

...

74 Commits

Author SHA1 Message Date
Fabrice de Gans
ca654aabfa [FAudio] Always convert UTF-16 names
On Windows, wchar_t is 16 bits and represents UTF-16 code units.
However, on other platforms, wchar_t is typically implemented as 32 bits
to represent UTF-32 code units. In order to work around this problem, we
always convert the string we get from FAudio, which is represented as
UTF-16 code units, into UTF-8 and let wxString handle the conversion to
its native type internally.
2024-08-17 20:25:51 -07:00
Rafael Kitover
2ce20c4f59 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-08-16 06:00:25 +00:00
Fabrice de Gans
e4ef4aa625 Propagate key events (#1323)
Previously, `wxEVT_KEY_DOWN` and `wxEVT_KEY_UP` were transformed into
`VBAM_EVT_USER_INPUT` and marked as processed early. This broke a number
of controls that rely on the traditional widgets events.

To fix this, we now filter the `wxEVT_KEY_*` and `wxEVT_CHAR` events in
the only widget we use that cares about them, `UserInputCtrl` instead of
filtering them at the application level.
2024-07-28 15:19:19 -07:00
Rafael Kitover
cf5cb40cb9 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-26 09:00:24 +00:00
Rafael Kitover
c450d14311 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-26 08:00:24 +00:00
Rafael Kitover
41572be3f2 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-24 11:00:23 +00:00
Rafael Kitover
4f8da1c574 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-24 09:00:24 +00:00
Rafael Kitover
32091669d4 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-23 12:00:29 +00:00
Rafael Kitover
abd72a5b2e translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-23 02:00:23 +00:00
Rafael Kitover
7e6349b19f translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-22 13:00:22 +00:00
Rafael Kitover
0782be749e translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-22 12:00:24 +00:00
Rafael Kitover
a7b545ab1a translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-15 03:00:22 +00:00
Rafael Kitover
1a564f900c translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-13 10:00:25 +00:00
Rafael Kitover
3d4e03f85f translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-08 12:00:23 +00:00
Rafael Kitover
fc17209ac7 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-06 05:00:23 +00:00
Rafael Kitover
38877ef209 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-05 21:00:24 +00:00
Rafael Kitover
8691a15be8 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-05 20:00:32 +00:00
Rafael Kitover
961fd0304c translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-05 20:00:15 +00:00
Rafael Kitover
e2cf6ecba6 Add option to mute sound during speedup
Add core option to mute sound during speedup, implemented in the gba
core. Link the option to the SDL port as well.

Add the new Mute Sound option as a checkbox to the xrc for the
Speedup/Turbo dialog and improve the dialog appearance.

Fix the max values for the options speedup_throttle and
speedup_frame_skip.

Add base class for numeric OptionProxy types implementing the +=, -=
operators, increment/decrement operators and operator T(). Add tests for
these operators on numeric types.

Move Speedup/Turbo dialog code from guiinit.cpp to a dialog class and
rewrite it using validators.

Remove the "GL VIEWPORT" debug prints from panel.cpp.

Fix #865

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-07-05 12:14:58 -07:00
Avindra Goolcharan
7a0826a60c Migrate vba-m.com links in .github folder 2024-06-21 18:02:38 -04:00
Rafael Kitover
d516683a77 build: fix for wx using GTK2
Use the __WXGTK3__ macro to check for GTK3 when setting the gdk backend
to X11 when Wayland EGL is not available, GTK2 does not have this
function and always uses X11 anyway.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-06-14 11:10:00 +00:00
Rafael Kitover
834c7de86c build: update macOS builder dists
Update sdl2, faudio, wxWidgets and ffmpeg to the latest versions for the
macOS builder.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-06-06 05:32:51 +00:00
Fabrice de Gans
4f1a5dd726 [Test] Add tests for widgets code
This creates a vbam-wx-widgets target for the custom widgets code and
adds tests for them.
2024-05-29 19:30:35 -07:00
Rafael Kitover
5766b9b9c7 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-27 10:00:25 +00:00
Rafael Kitover
5d8426d317 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-27 09:00:26 +00:00
Rafael Kitover
63ec3528f1 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-26 07:00:24 +00:00
Fabrice de Gans
f646c3848c [Test] Change assert to custom CHECK macros
This changes the way we handle asserts to use a set of custom macros.
This greatly speeds up crashing, especially on Windows, and provides
assertions in release mode too.
2024-05-21 18:47:50 +00:00
Fabrice de Gans
09433875bc [CI] Remove workaround for MSVC CI
Upstream GitHub actions now uses a single toolchain version.
Fixes #1297
2024-05-19 16:14:22 -07:00
Fabrice de Gans
05c09ff506 [Build] Add devkitpro-based libretro targets to CI 2024-05-10 13:36:05 -07:00
Rafael Kitover
7f78fbb3c5 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-10 03:00:24 +00:00
Rafael Kitover
261e26f488 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-10 02:00:25 +00:00
Rafael Kitover
ed820708af translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-10 01:00:27 +00:00
Fabrice de Gans
5b8b6a0b47 [Test] Add tests for the EmulatedGamepad class 2024-05-09 17:48:48 -07:00
Fabrice de Gans
8809ce26b3 [Test] Add tests for the Bindings class
In addition to fixing a couple of minor bugs, this also creates some
utility functions to access the cmdtab data.

Finally, this disables tests on MSYS2 as they can be flaky.
2024-05-09 13:15:16 -07:00
Fabrice de Gans
a48625855b [Build] Share wx-related targets configuration
Breaking the main wx target down to multiple libraries had the side
effect that many build options were not properly applied to libraries.
This fixes the issue by having a common configuration function in CMake
to share most of the configuration between all of the wx-related
targets.
2024-05-09 12:28:05 -07:00
Rafael Kitover
55c1477d69 build: disable FAudio for 32 bit Windows builds
Disable FAudio for 32 bit Windows builds because it uses libraries that
Windows XP does not have.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-09 02:34:25 +00:00
Rafael Kitover
2d7a1ea25b build: fix faudio linkage regression on MSVC+vcpkg
Followup on 244149c0 (build: fix faudio static linkage, 2024-05-09)
which broke linking faudio on MSVC+vcpkg, go back to using the
FAudio::FAudio cmake target for MSVC.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-09 01:38:44 +00:00
Rafael Kitover
244149c00e build: fix faudio static linkage
Use "faudio" and dependent dlls on Windows instead of the FAudio::FAudio
cmake target, because the cmake target always links the faudio dll.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-09 01:25:05 +00:00
Fabrice de Gans
c0bcf3bfdf [Test] Add tests for many config classes
This also fixes a coupld of minor issues that were found while adding
tests.
2024-05-08 13:08:07 -07:00
Fabrice de Gans
13756bcbf9 [Test] Replace doctest with googletest
* Convert the only existing test (strutils) to googletest.
* Create a test target for vbam-wx-config.
* Add necessary fakes for the test binary.
* Add tests to CI.
2024-05-08 13:00:56 -07:00
Rafael Kitover
fc82e06270 build: do not build SDL bin on Windows or macOS
Stop enabling the SDL binary by default on Windows or macOS because it
usually only of interest to Linux users.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-08 12:53:52 +00:00
Rafael Kitover
df89beb256 build: disable gpg signatures by default
Add the GPG_SIGNATURES cmake option to control creating gpg clearsign
signatures for the translations and exe zips for UPSTREAM_RELEASE,
defaulting to OFF.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-07 02:59:17 +00:00
Rafael Kitover
82eda48e8f translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-06 06:00:30 +00:00
Rafael Kitover
b47787b317 translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-06 06:00:12 +00:00
Fabrice de Gans
c9668d9a88 [config] Create the vbam-wx-config target
Circular dependencies between the config sub-module and the rest of the
wx frontend have been removed. This change separates the config code to
its own submodule.

This is a preliminary change to improve testing coverage.
2024-05-05 22:57:47 -07:00
Fabrice de Gans
90a56c6937 [config] Move strutils to src/config/
This removes circular dependencies between the main wx target and the
config/ sub-directory.
2024-05-05 21:38:14 -07:00
Fabrice de Gans
d377f7abff [CI] Install only one MSVC toolchain
A change in GitHub Actions broke our MSVC build flow due to different
toolchains being used at different steps of the build process. While the
upstream issue will eventually be fixed, we need to explicitly specify
the toolchain version for now.

Bug: #1297
2024-05-05 14:54:58 -07:00
Rafael Kitover
1fac129746 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 15:00:20 +00:00
Rafael Kitover
af7d5f7b89 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 14:00:21 +00:00
Rafael Kitover
28f7c2010b translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 10:00:20 +00:00
Rafael Kitover
486330f23d Activate GitHub Sponsors
Add .github/FUNDING.yml with my username to activate GitHub Sponsors for
this repository.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 01:27:11 +00:00
Rafael Kitover
1ae78a04a8 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 00:00:26 +00:00
Rafael Kitover
c776da7120 translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-05-05 00:00:08 +00:00
Fabrice de Gans
d543784a3d [UserInput] Filter key events globally
This changes key events to be filtered globally by the application,
preventing any widget from accessing them. Widgets should instead use
the new VBAM_USER_INPUT_EVENT event.

In addition, this explicitly removes accelerator handling from wxWidgets
main menu, allowing us to populate joystick options in the menu without
firing wxWidgets assertions.
2024-05-04 16:07:25 -07:00
Fabrice de Gans
902c6c8e4b [UserInput] Only process shortcut commands once
This modifies the UserInputEvent class to fire a vector of events at
once, rather than individual down/up events for each UserInput. This
simplifies handling of the global event filter and prevents the firing
of spurious events.

This also fixes a bug when pressing "Ctrl+1" would trigger the command
for both the command assigned to "Ctrl+1" and to "1". Now, only the
"Ctrl+1" command will fire.
2024-05-04 13:38:43 -07:00
Fabrice de Gans
d32be9ddbe Move cmdtab and command enable flags to config/
* Moves cmdtab to config/. This removes the dependency on the wxvbam.h
  header from the config/ directory.
* Fixes a number of issues in the shortcuts configuration window:
  * The "Remove" command was not working properly due to an incorrect
    refactor.
  * The window is now fully expandable.
2024-05-04 10:44:06 -07:00
Fabrice de Gans
b776509287 [bindings] Set default shortcut for recent file 3
This was lost in translation between 2 refactors.
2024-05-03 17:30:51 -07:00
Rafael Kitover
56eb97c846 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-29 13:00:23 +00:00
Rafael Kitover
9f46c575fd translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-29 10:00:21 +00:00
Rafael Kitover
e0402a9b0b translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-28 20:00:23 +00:00
Rafael Kitover
3e30f54d5f translations: fix strings starting with lowercase
Fix translated strings that start with lowercase to start with
uppercase.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-28 19:16:26 +00:00
Rafael Kitover
d73085a88c translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-25 08:00:21 +00:00
Rafael Kitover
8eb6a6900f translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-25 04:00:20 +00:00
Rafael Kitover
3615137c12 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-24 19:00:20 +00:00
Rafael Kitover
bbd5b76f2a translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-24 02:00:25 +00:00
Rafael Kitover
bb3604f333 translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-24 02:00:07 +00:00
Fabrice de Gans
18a0067ca7 [Input] Unify command handling
This unifies command handling between game and shortcut commands. Both
types of commands are now handled in a common manner and the binding
configuration is shared. In particular, this prevents assigning the same
user input (joypad or keyboard) to a game command or a shortcut command.

Bug: #745
2024-04-23 18:02:25 -07:00
Fabrice de Gans
cfdbdc4ec2 [Input] Move input configuration objects to app
Previously, some input-related configuration objects were either owned
by `gopts` or global values. This moves these objects to be owned by the
app object instead.

Rather than directly accessing the app object, other objects (like
dialogs) that need to access the input-related configuration objects are
passed a `ConfigurationObjectProvider` function. This will make it
easier to test these objects independently down the line.

Bug: #745
2024-04-23 15:38:39 -07:00
Rafael Kitover
32ca2ae42f translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-23 22:00:25 +00:00
Rafael Kitover
bad96cf91e translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-23 22:00:07 +00:00
Fabrice de Gans
62294702e4 [Input] Remove transitional key, mod, joy triplet
Originally, UserInput was built around the key, mod, joy triplet values
to maintain compatibility with other parts of the code base that made
use of these values. Since the UserInput class was introduced, all use
cases have been transitioned off these values in favor of treating the
UserInput as an abstract input.

UserInput is now a simple wrapper around either a KeyboardInput or a
JoyInput structure that only contain data pertinent to their input type.

This concludes the transition to the UserInput type.

Bug: #745
2024-04-23 14:26:59 -07:00
Rafael Kitover
72c4f33d63 translations: transifex pull
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-23 19:00:27 +00:00
Rafael Kitover
3fe57f540d translations: rebuild source .pot
Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2024-04-23 19:00:07 +00:00
Fabrice de Gans
1e1a369c8d [Input] Unify UserInput event handling
This adds a custom UserInputEvent for handling joypad and keyboard input
for both accelerators and emulator control configuration.

These changes fix a number of issues with the wxWidgets implementation
of key down / up event handling. In particular, every "down" event now
has a corresponding "up" event. All of the special handling that was
done in multiple places to handle shortcuts is now done in a new class,
`UserInputEventSender`, simplifying multiple call sites.

This is another step towards complete unification of UserInput handling,
which will prevent double assignment between shortcuts and emulator
controls.

Bug: #745
2024-04-23 11:30:20 -07:00
176 changed files with 37212 additions and 38849 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: rkitover

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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 }}

View File

@@ -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'

View File

@@ -1,4 +1,4 @@
name: MSYS2 Build
name: MSYS2
on: [push, pull_request]
jobs:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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
)

View File

@@ -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}")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -136,3 +136,5 @@ if(ENABLE_LINK)
PRIVATE ${NLS_LIBS}
)
endif()
add_subdirectory(test)

View File

@@ -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
View 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_

View File

@@ -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;
@@ -125,4 +126,4 @@ extern int systemSpeed;
#define SYSTEM_SAVE_UPDATED 30
#define SYSTEM_SAVE_NOT_UPDATED 0
#endif // VBAM_CORE_BASE_SYSTEM_H_
#endif // VBAM_CORE_BASE_SYSTEM_H_

View 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)

View 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_

View File

@@ -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;

View File

@@ -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"

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,10 +3861,23 @@ 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) {
soundSetThrottle(DowncastU16(last_throttle));
speedup_throttle_set = false;
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)

View 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
)

View 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;

View File

@@ -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
@@ -322,17 +322,18 @@ else ifeq ($(platform), libnx)
TARGET := $(TARGET_NAME)_libretro_$(platform).a
DEFINES := -DSWITCH=1 -U__linux__ -U__linux -DRARCH_INTERNAL -DHAVE_THREADS=1
CFLAGS := $(DEFINES) -g -O3 \
-fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec -Wl,--allow-multiple-definition -specs=$(LIBNX)/switch.specs
-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

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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}

View File

@@ -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;
}
}

View File

@@ -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});
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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_

View File

@@ -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;

View File

@@ -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_

View File

@@ -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

View File

@@ -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()
{

View File

@@ -4,8 +4,6 @@
#include <wx/string.h>
#include <wx/dynlib.h>
#include "winsparkle-rc.h"
class WinSparkleDllWrapper {
public:
static WinSparkleDllWrapper *GetInstance();

View File

@@ -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;

View File

@@ -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

View File

@@ -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()

View 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
View 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
View 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
View 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
View 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_

View 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
View 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
View 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_

View File

@@ -4,29 +4,34 @@ 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
STRING(REGEX REPLACE "^[^\"]*\\((wxID_|)([^,]*),[^\"]*(\"[^\"]*\")[^,)]*(,[^)]*|).*"
" new_cmditem(\"\\2\", \\3, XRCID(\"\\1\\2\")\\4 )"
EV "${EV}")
EV "${EV}")
STRING(REGEX REPLACE "XRCID\\(\"(wxID_[^\"]*)\"\\)" "\\1" EV ${EV})
LIST(APPEND EVLINES "${EV},\n")
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")
@@ -48,9 +53,9 @@ FOREACH(EV ${MW})
STRING(REGEX REPLACE "[^\"]*\\(" "" EV "${EV}")
STRING(REGEX REPLACE ",.*" "" EV "${EV}")
IF("${EV}" MATCHES "wx.*")
FILE(APPEND "${EVTABLE}" "${EV}")
FILE(APPEND "${EVTABLE}" "${EV}")
ELSE("${EV}" MATCHES "wx.*")
FILE(APPEND "${EVTABLE}" "XRCID(\"${EV}\")")
FILE(APPEND "${EVTABLE}" "XRCID(\"${EV}\")")
ENDIF("${EV}" MATCHES "wx.*")
FILE(APPEND "${EVTABLE}" ", MainFrame::On${EV})\n")
ENDFOREACH(EV)

View 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);
}

View 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

View 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_

View File

@@ -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

View File

@@ -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_

View 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

View 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

View File

@@ -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));
string_to_option_id.emplace(kAllOptionsData[i].config_name, static_cast<OptionID>(i));
}
assert(kStringToOptionId.size() == kNbOptions);
}
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));
string_to_filter.emplace(kFilterStrings[i], static_cast<Filter>(i));
}
assert(kStringToFilter.size() == kNbFilters);
}
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));
string_to_interframe.emplace(kInterframeStrings[i], static_cast<Interframe>(i));
}
assert(kStringToInterframe.size() == kNbInterframes);
}
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));
string_to_render_method.emplace(kRenderMethodStrings[i], static_cast<RenderMethod>(i));
}
assert(kStringToRenderMethod.size() == kNbRenderMethods);
}
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));
string_to_audio_api.emplace(kAudioApiStrings[i], static_cast<AudioApi>(i));
}
assert(kStringToAudioApi.size() == kNbAudioApis);
}
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));
string_to_sound_quality.emplace(kAudioRateStrings[i], static_cast<AudioRate>(i));
}
assert(kStringToSoundQuality.size() == kNbSoundRate);
}
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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -99,6 +99,7 @@ enum class OptionID {
kPrefSpeedupThrottle,
kPrefSpeedupFrameSkip,
kPrefSpeedupThrottleFrameSkip,
kPrefSpeedupMute,
kPrefUseBiosGB,
kPrefUseBiosGBA,
kPrefUseBiosGBC,

View File

@@ -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;

View File

@@ -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_;

View 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());
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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