mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/input-revamp' into medusa
This commit is contained in:
commit
09cff0779e
42
CHANGES
42
CHANGES
|
@ -7,6 +7,10 @@ Bugfixes:
|
|||
Misc:
|
||||
- DS GX: Clean up and unify texture mapping
|
||||
|
||||
0.7.0: (Future)
|
||||
Misc:
|
||||
- GBA Timer: Use global cycles for timers
|
||||
|
||||
0.6.0: (Future)
|
||||
Features:
|
||||
- GBA: Support printing debug strings from inside a game
|
||||
|
@ -34,6 +38,7 @@ Features:
|
|||
- LR35902: Watchpoints
|
||||
- Memory search
|
||||
- Debugger: Execution tracing
|
||||
- Qt: Italian translation (by theheroGAC)
|
||||
Bugfixes:
|
||||
- LR35902: Fix core never exiting with certain event patterns
|
||||
- GB Timer: Improve DIV reset behavior
|
||||
|
@ -78,6 +83,14 @@ Bugfixes:
|
|||
- GB Timer: Fix DIV batching if TAC changes
|
||||
- GB Video: Reset renderer when loading state
|
||||
- GBA BIOS: Fix INT_MIN/-1 crash
|
||||
- GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658)
|
||||
- OpenGL: Fix some shaders causing offset graphics
|
||||
- Qt: Fix game unpausing after frame advancing and refocusing
|
||||
- GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering
|
||||
- Core: Fix interrupting a thread while on the thread (fixes mgba.io/i/692)
|
||||
- Core: Fix directory sets crashing on close if base isn't properly detached
|
||||
- Qt: Fix window icon being stretched
|
||||
- Qt: Fix data directory path
|
||||
Misc:
|
||||
- SDL: Remove scancode key input
|
||||
- GBA Video: Clean up unused timers
|
||||
|
@ -142,6 +155,35 @@ Misc:
|
|||
- Core: Move savestate creation time to extdata
|
||||
- Debugger: Add mDebuggerRunFrame convenience function
|
||||
- GBA Memory: Remove unused prefetch cruft
|
||||
- GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726)
|
||||
- Core: Config values can now be hexadecimal
|
||||
- GB: Reset with initial state of DIV register
|
||||
- GB MBC: New MBC7 implementation
|
||||
- Qt: Better highlight active key in control binding
|
||||
- Core: Improved threading interrupted detection
|
||||
- GBA Timer: Improve accuracy of timers
|
||||
|
||||
0.6 beta 2: (Future)
|
||||
Features:
|
||||
- Qt: Italian translation (by theheroGAC)
|
||||
- Qt: Updated German translation
|
||||
Bugfixes:
|
||||
- Qt: Fix memory search close button (fixes mgba.io/i/769)
|
||||
- Qt: Fix window icon being stretched
|
||||
- Qt: Fix initial window size (fixes mgba.io/i/766)
|
||||
- Qt: Fix data directory path
|
||||
- Qt: Fix controls not saving on non-SDL builds
|
||||
- GB Video: Fix LYC regression
|
||||
- Qt: Fix translation initialization (fixes mgba.io/i/776)
|
||||
- PSP2: Use custom localtime_r since newlib version is broken (fixes mgba.io/i/560)
|
||||
Misc:
|
||||
- Qt: Add language selector
|
||||
- GBA Timer: Improve accuracy of timers
|
||||
- Qt: Minor test fixes
|
||||
- PSP2: Update toolchain to use vita.cmake
|
||||
|
||||
0.6 beta 1: (2017-06-29)
|
||||
- Initial beta for 0.6
|
||||
|
||||
medusa alpha 2: (2017-04-26)
|
||||
Features:
|
||||
|
|
|
@ -280,7 +280,9 @@ endif()
|
|||
include(CheckFunctionExists)
|
||||
check_function_exists(strdup HAVE_STRDUP)
|
||||
check_function_exists(strndup HAVE_STRNDUP)
|
||||
check_function_exists(localtime_r HAVE_LOCALTIME_R)
|
||||
if(NOT DEFINED PSP2)
|
||||
check_function_exists(localtime_r HAVE_LOCALTIME_R)
|
||||
endif()
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic")
|
||||
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
|
@ -874,7 +876,6 @@ if(BUILD_EXAMPLE)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS ${USE_PTHREADS})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME})
|
||||
|
||||
|
@ -928,6 +929,7 @@ else()
|
|||
endif()
|
||||
|
||||
if(NOT QUIET)
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
message(STATUS "Platforms:")
|
||||
message(STATUS " Game Boy Advance: ${M_CORE_GBA}")
|
||||
message(STATUS " Game Boy: ${M_CORE_GB}")
|
||||
|
|
16
README.md
16
README.md
|
@ -30,7 +30,7 @@ Features
|
|||
- Remappable controls for both keyboards and gamepads.
|
||||
- Loading from ZIP and 7z files.
|
||||
- IPS, UPS and BPS patch support.
|
||||
- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro.
|
||||
- Game debugging via a command-line interface and GDB remote support, compatible with IDA Pro.
|
||||
- Configurable emulation rewinding.
|
||||
- Support for loading and exporting GameShark and Action Replay snapshots.
|
||||
- Cores available for RetroArch/Libretro and OpenEmu.
|
||||
|
@ -112,6 +112,16 @@ Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to
|
|||
|
||||
This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them.
|
||||
|
||||
If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are:
|
||||
|
||||
brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
|
||||
make
|
||||
|
||||
Note that you should not do a `make install` on macOS, as it will not work properly.
|
||||
|
||||
#### Windows developer building
|
||||
|
||||
To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 500MiB of packages, so it will take a long time):
|
||||
|
@ -151,7 +161,7 @@ medusa has no hard dependencies, however, the following optional dependencies ar
|
|||
- ImageMagick: for GIF recording.
|
||||
- SQLite3: for game databases.
|
||||
|
||||
Both libpng and zlib are included with the emulator, so they do not need to be externally compiled first.
|
||||
SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first.
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
@ -203,7 +213,7 @@ Missing features on DS are
|
|||
|
||||
<a name="flashdetect">[3]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
|
||||
|
||||
<a name="osxver">[4]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
|
||||
<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.5, and may work on older.
|
||||
|
||||
[downloads]: http://mgba.io/downloads.html
|
||||
[source]: https://github.com/mgba-emu/mgba/
|
||||
|
|
|
@ -31,6 +31,10 @@ typedef uint32_t color_t;
|
|||
#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5)
|
||||
#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5)
|
||||
|
||||
#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19))
|
||||
#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9))
|
||||
#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19))
|
||||
|
||||
struct blip_t;
|
||||
|
||||
struct mCoreCallbacks {
|
||||
|
|
|
@ -36,6 +36,7 @@ void mLibraryDestroy(struct mLibrary*);
|
|||
struct VDir;
|
||||
struct VFile;
|
||||
void mLibraryLoadDirectory(struct mLibrary* library, const char* base);
|
||||
void mLibraryClear(struct mLibrary* library);
|
||||
|
||||
size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints);
|
||||
size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints);
|
||||
|
|
|
@ -38,6 +38,7 @@ bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent*
|
|||
int32_t mTimingTick(struct mTiming* timing, int32_t cycles);
|
||||
int32_t mTimingCurrentTime(const struct mTiming* timing);
|
||||
int32_t mTimingNextEvent(struct mTiming* timing);
|
||||
int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ CXX_GUARD_START
|
|||
struct ARMCore;
|
||||
struct DS;
|
||||
void DSTimerInit(struct DS* ds);
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control);
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -67,21 +67,23 @@ typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_
|
|||
typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address);
|
||||
|
||||
DECL_BITFIELD(GBMBC7Field, uint8_t);
|
||||
DECL_BIT(GBMBC7Field, SK, 6);
|
||||
DECL_BIT(GBMBC7Field, CS, 7);
|
||||
DECL_BIT(GBMBC7Field, IO, 1);
|
||||
DECL_BIT(GBMBC7Field, CLK, 6);
|
||||
DECL_BIT(GBMBC7Field, DI, 1);
|
||||
DECL_BIT(GBMBC7Field, DO, 0);
|
||||
|
||||
enum GBMBC7MachineState {
|
||||
GBMBC7_STATE_NULL = -1,
|
||||
GBMBC7_STATE_IDLE = 0,
|
||||
GBMBC7_STATE_READ_COMMAND = 1,
|
||||
GBMBC7_STATE_READ_ADDRESS = 2,
|
||||
GBMBC7_STATE_COMMAND_0 = 3,
|
||||
GBMBC7_STATE_COMMAND_SR_WRITE = 4,
|
||||
GBMBC7_STATE_COMMAND_SR_READ = 5,
|
||||
GBMBC7_STATE_COMMAND_SR_FILL = 6,
|
||||
GBMBC7_STATE_READ = 7,
|
||||
GBMBC7_STATE_WRITE = 8,
|
||||
GBMBC7_STATE_DO = 2,
|
||||
|
||||
GBMBC7_STATE_EEPROM_EWDS = 0x10,
|
||||
GBMBC7_STATE_EEPROM_WRAL = 0x11,
|
||||
GBMBC7_STATE_EEPROM_ERAL = 0x12,
|
||||
GBMBC7_STATE_EEPROM_EWEN = 0x13,
|
||||
GBMBC7_STATE_EEPROM_WRITE = 0x14,
|
||||
GBMBC7_STATE_EEPROM_READ = 0x18,
|
||||
GBMBC7_STATE_EEPROM_ERASE = 0x1C,
|
||||
};
|
||||
|
||||
struct GBMBC1State {
|
||||
|
@ -91,12 +93,13 @@ struct GBMBC1State {
|
|||
|
||||
struct GBMBC7State {
|
||||
enum GBMBC7MachineState state;
|
||||
uint32_t sr;
|
||||
uint16_t sr;
|
||||
uint8_t address;
|
||||
bool writable;
|
||||
int srBits;
|
||||
int command;
|
||||
GBMBC7Field field;
|
||||
uint8_t access;
|
||||
uint8_t latch;
|
||||
GBMBC7Field eeprom;
|
||||
};
|
||||
|
||||
struct GBPocketCamState {
|
||||
|
|
|
@ -16,6 +16,8 @@ struct GBCartridgeOverride {
|
|||
int headerCrc32;
|
||||
enum GBModel model;
|
||||
enum GBMemoryBankControllerType mbc;
|
||||
|
||||
uint32_t gbColors[4];
|
||||
};
|
||||
|
||||
struct Configuration;
|
||||
|
|
|
@ -144,7 +144,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value);
|
|||
void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value);
|
||||
void GBVideoSwitchBank(struct GBVideo* video, uint8_t value);
|
||||
|
||||
void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color);
|
||||
void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color);
|
||||
|
||||
struct GBSerializedState;
|
||||
void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state);
|
||||
|
|
|
@ -264,10 +264,10 @@ struct GBASerializedState {
|
|||
|
||||
struct {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
uint16_t reserved;
|
||||
uint32_t lastEvent;
|
||||
uint32_t nextEvent;
|
||||
int32_t overflowInterval;
|
||||
uint32_t nextIrq;
|
||||
GBATimerFlags flags;
|
||||
} timers[4];
|
||||
|
||||
|
|
|
@ -17,13 +17,13 @@ DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4);
|
|||
DECL_BIT(GBATimerFlags, CountUp, 4);
|
||||
DECL_BIT(GBATimerFlags, DoIrq, 5);
|
||||
DECL_BIT(GBATimerFlags, Enable, 6);
|
||||
DECL_BIT(GBATimerFlags, IrqPending, 7);
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
uint32_t lastEvent;
|
||||
int32_t lastEvent;
|
||||
struct mTimingEvent event;
|
||||
int32_t overflowInterval;
|
||||
struct mTimingEvent irq;
|
||||
GBATimerFlags flags;
|
||||
int forcedPrescale;
|
||||
};
|
||||
|
@ -31,13 +31,13 @@ struct GBATimer {
|
|||
struct ARMCore;
|
||||
struct GBA;
|
||||
void GBATimerInit(struct GBA* gba);
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload);
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control);
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer);
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew);
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate);
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew);
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload);
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ Fog
|
|||
Reilly Grant
|
||||
Philip Horton
|
||||
Jordan Jorgensen
|
||||
mars
|
||||
Rohit Nirmal
|
||||
Rhys Powell
|
||||
rootfather
|
||||
|
|
|
@ -7,5 +7,5 @@ passes=1
|
|||
[pass.0]
|
||||
fragmentShader=agb001.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
|
|
@ -7,13 +7,13 @@ passes=2
|
|||
[pass.0]
|
||||
fragmentShader=ags001.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
||||
[pass.1]
|
||||
fragmentShader=ags001-light.fs
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
||||
[pass.1.uniform.lightBrightness]
|
||||
type=float
|
||||
|
|
|
@ -23,19 +23,15 @@
|
|||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
precision highp float;
|
||||
|
||||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
|
||||
uniform float similarity_threshold;
|
||||
|
||||
#define screen_res 240,160
|
||||
|
||||
vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported
|
||||
{
|
||||
return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) );
|
||||
return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * texSize) );
|
||||
}
|
||||
|
||||
float pixel_brightness(vec4 pixel)
|
||||
|
@ -140,7 +136,7 @@ vec4 interpolate_diagonal(vec4 a, vec4 b, vec4 c, vec4 d)
|
|||
|
||||
void main()
|
||||
{
|
||||
ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2);
|
||||
ivec2 pixel_coords2 = ivec2(texCoord * texSize * 2);
|
||||
ivec2 pixel_coords = pixel_coords2 / 2;
|
||||
|
||||
bool x_even = mod(pixel_coords2.x,2) == 0;
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
LCD Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float boundBrightness;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
|
||||
if (int(mod(texCoord.s * texSize.x * 3.0, 3.0)) == 0 ||
|
||||
int(mod(texCoord.t * texSize.y * 3.0, 3.0)) == 0)
|
||||
{
|
||||
color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness;
|
||||
}
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=LCD
|
||||
author=Dominus Iniquitatis
|
||||
description=Simple LCD emulation.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=lcd.fs
|
||||
blend=1
|
||||
width=-3
|
||||
height=-3
|
||||
|
||||
[pass.0.uniform.boundBrightness]
|
||||
type=float
|
||||
readableName=Bound brightness
|
||||
default=0.9
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=Motion Blur
|
||||
author=Dominus Iniquitatis
|
||||
description=Simple motion blur.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=motion_blur.fs
|
||||
blend=1
|
||||
width=-1
|
||||
height=-1
|
||||
|
||||
[pass.0.uniform.amount]
|
||||
type=float
|
||||
readableName=Amount
|
||||
default=0.3
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Motion Blur Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float amount;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
color.a = 1.0 - amount;
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=Scanlines
|
||||
author=Dominus Iniquitatis
|
||||
description=Simple scanlines.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=scanlines.fs
|
||||
blend=1
|
||||
width=-2
|
||||
height=-2
|
||||
|
||||
[pass.0.uniform.lineBrightness]
|
||||
type=float
|
||||
readableName=Line brightness
|
||||
default=0.5
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
Scanlines Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float lineBrightness;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
|
||||
if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0)
|
||||
{
|
||||
color.rgb *= vec3(1.0, 1.0, 1.0) * lineBrightness;
|
||||
}
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=Soften
|
||||
author=Dominus Iniquitatis
|
||||
description=Soft image blurring.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=soften.fs
|
||||
blend=1
|
||||
width=-1
|
||||
height=-1
|
||||
|
||||
[pass.0.uniform.amount]
|
||||
type=float
|
||||
readableName=Amount
|
||||
default=0.5
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Soften Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float amount;
|
||||
|
||||
vec2 GetTexelSize()
|
||||
{
|
||||
return vec2(1.0 / texSize.x, 1.0 / texSize.y);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
|
||||
vec4 northColor = texture2D(tex, texCoord + vec2(0.0, GetTexelSize().y));
|
||||
vec4 southColor = texture2D(tex, texCoord - vec2(0.0, GetTexelSize().y));
|
||||
vec4 eastColor = texture2D(tex, texCoord + vec2(GetTexelSize().x, 0.0));
|
||||
vec4 westColor = texture2D(tex, texCoord - vec2(GetTexelSize().x, 0.0));
|
||||
|
||||
if (abs(length(color) - length(northColor)) > 0.0)
|
||||
{
|
||||
color = mix(color, northColor, amount / 4.0);
|
||||
}
|
||||
|
||||
if (abs(length(color) - length(southColor)) > 0.0)
|
||||
{
|
||||
color = mix(color, southColor, amount / 4.0);
|
||||
}
|
||||
|
||||
if (abs(length(color) - length(eastColor)) > 0.0)
|
||||
{
|
||||
color = mix(color, eastColor, amount / 4.0);
|
||||
}
|
||||
|
||||
if (abs(length(color) - length(westColor)) > 0.0)
|
||||
{
|
||||
color = mix(color, westColor, amount / 4.0);
|
||||
}
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=VBA Pixelate
|
||||
author=Dominus Iniquitatis
|
||||
description=VisualBoyAdvance-style pixelation.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=vba_pixelate.fs
|
||||
blend=1
|
||||
width=-2
|
||||
height=-2
|
||||
|
||||
[pass.0.uniform.boundBrightness]
|
||||
type=float
|
||||
readableName=Bound brightness
|
||||
default=0.5
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
VBA Pixelate Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float boundBrightness;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
|
||||
if (int(mod(texCoord.s * texSize.x * 2.0, 2.0)) == 0 ||
|
||||
int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0)
|
||||
{
|
||||
color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness;
|
||||
}
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[shader]
|
||||
name=Vignette
|
||||
author=Dominus Iniquitatis
|
||||
description=Configurable vignette effect.
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=vignette.fs
|
||||
blend=1
|
||||
width=-1
|
||||
height=-1
|
||||
|
||||
[pass.0.uniform.intensity]
|
||||
type=float
|
||||
readableName=Intensity
|
||||
default=1.0
|
||||
min=0.0
|
||||
max=1.0
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Vignette Shader
|
||||
|
||||
Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform float intensity;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(tex, texCoord);
|
||||
color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), length(texCoord - 0.5) * intensity);
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -7,5 +7,5 @@ passes=1
|
|||
[pass.0]
|
||||
fragmentShader=wiiu.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
|
|
@ -86,6 +86,9 @@ static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, i
|
|||
}
|
||||
char* end;
|
||||
long value = strtol(charValue, &end, 10);
|
||||
if (end == &charValue[1] && *end == 'x') {
|
||||
value = strtol(charValue, &end, 16);
|
||||
}
|
||||
if (*end) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include <mgba/feature/video-logger.h>
|
||||
#endif
|
||||
|
||||
const static struct mCoreFilter {
|
||||
static const struct mCoreFilter {
|
||||
bool (*filter)(struct VFile*);
|
||||
struct mCore* (*open)(void);
|
||||
enum mPlatform platform;
|
||||
|
|
|
@ -22,28 +22,58 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
|||
mDirectorySetDetachBase(dirs);
|
||||
|
||||
if (dirs->archive) {
|
||||
if (dirs->archive == dirs->save) {
|
||||
dirs->save = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->patch) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = 0;
|
||||
dirs->archive = NULL;
|
||||
}
|
||||
|
||||
if (dirs->save) {
|
||||
if (dirs->save == dirs->patch) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
dirs->save->close(dirs->save);
|
||||
dirs->save = 0;
|
||||
dirs->save = NULL;
|
||||
}
|
||||
|
||||
if (dirs->patch) {
|
||||
if (dirs->patch == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
dirs->patch->close(dirs->patch);
|
||||
dirs->patch = 0;
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
|
||||
if (dirs->state) {
|
||||
if (dirs->state == dirs->screenshot) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
dirs->state->close(dirs->state);
|
||||
dirs->state = 0;
|
||||
dirs->state = NULL;
|
||||
}
|
||||
|
||||
if (dirs->screenshot) {
|
||||
dirs->screenshot->close(dirs->screenshot);
|
||||
dirs->screenshot = 0;
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,21 +95,21 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
|||
|
||||
void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
|
||||
if (dirs->save == dirs->base) {
|
||||
dirs->save = 0;
|
||||
dirs->save = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->base) {
|
||||
dirs->patch = 0;
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->state == dirs->base) {
|
||||
dirs->state = 0;
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->screenshot == dirs->base) {
|
||||
dirs->screenshot = 0;
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
|
||||
if (dirs->base) {
|
||||
dirs->base->close(dirs->base);
|
||||
dirs->base = 0;
|
||||
dirs->base = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -339,6 +339,16 @@ static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry*
|
|||
sqlite3_step(library->insertPath);
|
||||
}
|
||||
|
||||
void mLibraryClear(struct mLibrary* library) {
|
||||
int result = sqlite3_exec(library->db,
|
||||
" BEGIN TRANSACTION;"
|
||||
"\n DELETE FROM roots;"
|
||||
"\n DELETE FROM roms;"
|
||||
"\n DELETE FROM paths;"
|
||||
"\n COMMIT;"
|
||||
"\n VACUUM;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) {
|
||||
sqlite3_clear_bindings(library->count);
|
||||
sqlite3_reset(library->count);
|
||||
|
|
|
@ -272,7 +272,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor
|
|||
|
||||
// Decimal:
|
||||
value = strtoull(valueStr, &end, 10);
|
||||
if (end) {
|
||||
if (end && !end[0]) {
|
||||
if (value > 0x10000) {
|
||||
found += _search32(mem, size, block, value, out, limit ? limit - found : 0);
|
||||
} else if (value > 0x100) {
|
||||
|
@ -305,7 +305,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor
|
|||
|
||||
// Hex:
|
||||
value = strtoull(valueStr, &end, 16);
|
||||
if (end) {
|
||||
if (end && !end[0]) {
|
||||
if (value > 0x10000) {
|
||||
found += _search32(mem, size, block, value, out, limit ? limit - found : 0);
|
||||
} else if (value > 0x100) {
|
||||
|
|
|
@ -401,11 +401,14 @@ void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
|
|||
MutexLock(&threadContext->stateMutex);
|
||||
++threadContext->interruptDepth;
|
||||
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
|
||||
if (threadContext->state == THREAD_INTERRUPTING) {
|
||||
threadContext->state = THREAD_INTERRUPTED;
|
||||
}
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return;
|
||||
}
|
||||
threadContext->savedState = threadContext->state;
|
||||
threadContext->state = THREAD_INTERRUPTING;
|
||||
threadContext->state = THREAD_INTERRUPTED;
|
||||
ConditionWake(&threadContext->stateCond);
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
|
@ -465,7 +468,7 @@ void mCoreThreadUnpause(struct mCoreThread* threadContext) {
|
|||
bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
|
||||
bool isPaused;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
|
||||
if (threadContext->interruptDepth) {
|
||||
isPaused = threadContext->savedState == THREAD_PAUSED;
|
||||
} else {
|
||||
isPaused = threadContext->state == THREAD_PAUSED;
|
||||
|
@ -495,7 +498,7 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
|
|||
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
|
||||
bool frameOn = true;
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING)) {
|
||||
if (threadContext->state == THREAD_RUNNING || (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING)) {
|
||||
threadContext->state = THREAD_PAUSING;
|
||||
frameOn = false;
|
||||
}
|
||||
|
@ -506,11 +509,11 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
|
|||
|
||||
void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_REWINDING))) {
|
||||
if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->interruptDepth && threadContext->savedState == THREAD_REWINDING))) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return;
|
||||
}
|
||||
if (!rewinding && (threadContext->state == THREAD_RUNNING || (threadContext->state == THREAD_INTERRUPTING && threadContext->savedState == THREAD_RUNNING))) {
|
||||
if (!rewinding && ((!threadContext->interruptDepth && threadContext->state != THREAD_REWINDING) || (threadContext->interruptDepth && threadContext->savedState != THREAD_REWINDING))) {
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
return;
|
||||
}
|
||||
|
@ -526,7 +529,7 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding)
|
|||
|
||||
void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_RUNNING) {
|
||||
if (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING) {
|
||||
threadContext->savedState = THREAD_WAITING;
|
||||
} else if (threadContext->state == THREAD_RUNNING) {
|
||||
threadContext->state = THREAD_WAITING;
|
||||
|
@ -536,7 +539,7 @@ void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
|
|||
|
||||
void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
|
||||
MutexLock(&threadContext->stateMutex);
|
||||
if ((threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) && threadContext->savedState == THREAD_WAITING) {
|
||||
if (threadContext->interruptDepth && threadContext->savedState == THREAD_WAITING) {
|
||||
threadContext->savedState = THREAD_RUNNING;
|
||||
} else if (threadContext->state == THREAD_WAITING) {
|
||||
threadContext->state = THREAD_RUNNING;
|
||||
|
|
|
@ -89,5 +89,9 @@ int32_t mTimingNextEvent(struct mTiming* timing) {
|
|||
if (!next) {
|
||||
return INT_MAX;
|
||||
}
|
||||
return next->when - timing->masterCycles;
|
||||
return next->when - timing->masterCycles - *timing->relativeCycles;
|
||||
}
|
||||
|
||||
int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent* event) {
|
||||
return event->when - timing->masterCycles - *timing->relativeCycles;
|
||||
}
|
||||
|
|
16
src/ds/io.c
16
src/ds/io.c
|
@ -78,19 +78,19 @@ static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t va
|
|||
|
||||
case DS_REG_TM0CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], value);
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[0], &dscore->timing, &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS_REG_TM1CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], value);
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[1], &dscore->timing, &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS_REG_TM2CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], value);
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[2], &dscore->timing, &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS_REG_TM3CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], value);
|
||||
DSTimerWriteTMCNT_HI(&dscore->timers[3], &dscore->timing, &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], value);
|
||||
break;
|
||||
|
||||
// IPC
|
||||
|
@ -243,16 +243,16 @@ static uint16_t DSIOReadKeyInput(struct DS* ds) {
|
|||
static void DSIOUpdateTimer(struct DSCommon* dscore, uint32_t address) {
|
||||
switch (address) {
|
||||
case DS_REG_TM0CNT_LO:
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[0], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[0], &dscore->timing, &dscore->memory.io[address >> 1], 0);
|
||||
break;
|
||||
case DS_REG_TM1CNT_LO:
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[1], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[1], &dscore->timing, &dscore->memory.io[address >> 1], 0);
|
||||
break;
|
||||
case DS_REG_TM2CNT_LO:
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[2], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[2], &dscore->timing, &dscore->memory.io[address >> 1], 0);
|
||||
break;
|
||||
case DS_REG_TM3CNT_LO:
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[3], &dscore->timing, dscore->cpu, &dscore->memory.io[address >> 1], 0);
|
||||
GBATimerUpdateRegisterInternal(&dscore->timers[3], &dscore->timing, &dscore->memory.io[address >> 1], 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,42 +8,58 @@
|
|||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/internal/ds/ds.h>
|
||||
|
||||
static void DSTimerIrq(struct DSCommon* dscore, int timerId) {
|
||||
struct GBATimer* timer = &dscore->timers[timerId];
|
||||
if (GBATimerFlagsIsIrqPending(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsClearIrqPending(timer->flags);
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0 + timerId);
|
||||
}
|
||||
}
|
||||
|
||||
static void DSTimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 0);
|
||||
}
|
||||
|
||||
static void DSTimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 1);
|
||||
}
|
||||
|
||||
static void DSTimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 2);
|
||||
}
|
||||
|
||||
static void DSTimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 3);
|
||||
}
|
||||
|
||||
static void DSTimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
struct GBATimer* timer = &dscore->timers[0];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0);
|
||||
}
|
||||
GBATimerUpdate(timing, &dscore->timers[0], &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void DSTimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
struct GBATimer* timer = &dscore->timers[1];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER1);
|
||||
}
|
||||
GBATimerUpdate(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void DSTimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
struct GBATimer* timer = &dscore->timers[2];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER2);
|
||||
}
|
||||
GBATimerUpdate(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void DSTimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
struct GBATimer* timer = &dscore->timers[3];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER3);
|
||||
}
|
||||
GBATimerUpdate(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
|
@ -65,6 +81,22 @@ void DSTimerInit(struct DS* ds) {
|
|||
ds->ds7.timers[3].event.callback = DSTimerUpdate3;
|
||||
ds->ds7.timers[3].event.context = &ds->ds7;
|
||||
ds->ds7.timers[3].event.priority = 0x23;
|
||||
ds->ds7.timers[0].irq.name = "DS7 Timer 0 IRQ";
|
||||
ds->ds7.timers[0].irq.callback = DSTimerIrq0;
|
||||
ds->ds7.timers[0].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[0].irq.priority = 0x28;
|
||||
ds->ds7.timers[1].irq.name = "DS7 Timer 1 IRQ";
|
||||
ds->ds7.timers[1].irq.callback = DSTimerIrq1;
|
||||
ds->ds7.timers[1].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[1].irq.priority = 0x29;
|
||||
ds->ds7.timers[2].irq.name = "DS7 Timer 2 IRQ";
|
||||
ds->ds7.timers[2].irq.callback = DSTimerIrq2;
|
||||
ds->ds7.timers[2].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[2].irq.priority = 0x2A;
|
||||
ds->ds7.timers[3].irq.name = "DS7 Timer 3 IRQ";
|
||||
ds->ds7.timers[3].irq.callback = DSTimerIrq3;
|
||||
ds->ds7.timers[3].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[3].irq.priority = 0x2B;
|
||||
|
||||
memset(ds->ds9.timers, 0, sizeof(ds->ds9.timers));
|
||||
ds->ds9.timers[0].event.name = "DS9 Timer 0";
|
||||
|
@ -87,9 +119,24 @@ void DSTimerInit(struct DS* ds) {
|
|||
ds->ds9.timers[3].event.context = &ds->ds9;
|
||||
ds->ds9.timers[3].event.priority = 0x23;
|
||||
ds->ds9.timers[3].forcedPrescale = 1;
|
||||
ds->ds9.timers[0].irq.name = "DS9 Timer 0 IRQ";
|
||||
ds->ds9.timers[0].irq.callback = DSTimerIrq0;
|
||||
ds->ds9.timers[0].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[0].irq.priority = 0x28;
|
||||
ds->ds9.timers[1].irq.name = "DS9 Timer 1 IRQ";
|
||||
ds->ds9.timers[1].irq.callback = DSTimerIrq1;
|
||||
ds->ds9.timers[1].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[1].irq.priority = 0x29;
|
||||
ds->ds9.timers[2].irq.name = "DS9 Timer 2 IRQ";
|
||||
ds->ds9.timers[2].irq.callback = DSTimerIrq2;
|
||||
ds->ds9.timers[2].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[2].irq.priority = 0x2A;
|
||||
ds->ds9.timers[3].irq.name = "DS9 Timer 3 IRQ";
|
||||
ds->ds9.timers[3].irq.callback = DSTimerIrq3;
|
||||
ds->ds9.timers[3].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[3].irq.priority = 0x2B;
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t value) {
|
||||
GBATimerUpdateRegisterInternal(timer, timing, cpu, io, 0);
|
||||
GBATimerWriteTMCNT_HI(timer, timing, cpu, io, value);
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t value) {
|
||||
GBATimerWriteTMCNT_HI(timer, timing, io, value);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
const char mVL_MAGIC[] = "mVL\0";
|
||||
|
||||
const static struct mVLDescriptor {
|
||||
static const struct mVLDescriptor {
|
||||
enum mPlatform platform;
|
||||
struct mCore* (*open)(void);
|
||||
} _descriptors[] = {
|
||||
|
|
|
@ -555,7 +555,7 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
|||
if (audio->ch2.envelope.dead == 2) {
|
||||
mTimingDeschedule(timing, &audio->ch2Event);
|
||||
}
|
||||
_updateSquareSample(&audio->ch1);
|
||||
_updateSquareSample(&audio->ch2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -709,7 +709,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) {
|
|||
}
|
||||
|
||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
||||
ch->sample = (ch->control.hi * ch->envelope.currentVolume - 8) * 0x10;
|
||||
ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8;
|
||||
}
|
||||
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
|
||||
|
|
|
@ -29,26 +29,26 @@
|
|||
#include <mgba/internal/gba/input.h>
|
||||
#endif
|
||||
|
||||
const static struct mCoreChannelInfo _GBVideoLayers[] = {
|
||||
static const struct mCoreChannelInfo _GBVideoLayers[] = {
|
||||
{ 0, "bg", "Background", NULL },
|
||||
{ 1, "obj", "Objects", NULL },
|
||||
{ 2, "win", "Window", NULL },
|
||||
};
|
||||
|
||||
const static struct mCoreChannelInfo _GBAudioChannels[] = {
|
||||
{ 0, "ch0", "Channel 0", "Square/Sweep" },
|
||||
{ 1, "ch1", "Channel 1", "Square" },
|
||||
{ 2, "ch2", "Channel 2", "PCM" },
|
||||
{ 3, "ch3", "Channel 3", "Noise" },
|
||||
static const struct mCoreChannelInfo _GBAudioChannels[] = {
|
||||
{ 0, "ch1", "Channel 1", "Square/Sweep" },
|
||||
{ 1, "ch2", "Channel 2", "Square" },
|
||||
{ 2, "ch3", "Channel 3", "PCM" },
|
||||
{ 3, "ch4", "Channel 4", "Noise" },
|
||||
};
|
||||
|
||||
const static struct LR35902Segment _GBSegments[] = {
|
||||
static const struct LR35902Segment _GBSegments[] = {
|
||||
{ .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM },
|
||||
{ .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
const static struct LR35902Segment _GBCSegments[] = {
|
||||
static const struct LR35902Segment _GBCSegments[] = {
|
||||
{ .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM },
|
||||
{ .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 },
|
||||
{ .name = "WRAM", .start = GB_BASE_WORKING_RAM_BANK1, .end = 0xE000 },
|
||||
|
@ -56,7 +56,7 @@ const static struct LR35902Segment _GBCSegments[] = {
|
|||
{ 0 }
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBMemoryBlocks[] = {
|
||||
static const struct mCoreMemoryBlock _GBMemoryBlocks[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
|
||||
{ GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 },
|
||||
{ GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
@ -67,7 +67,7 @@ const static struct mCoreMemoryBlock _GBMemoryBlocks[] = {
|
|||
{ GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
|
||||
static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
|
||||
{ GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 },
|
||||
{ GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
|
||||
|
|
|
@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) {
|
|||
cpu->e = 0xD8;
|
||||
cpu->h = 1;
|
||||
cpu->l = 0x4D;
|
||||
gb->timer.internalDiv = 0x2AF3;
|
||||
break;
|
||||
case GB_MODEL_AGB:
|
||||
cpu->b = 1;
|
||||
|
@ -444,6 +445,7 @@ void GBReset(struct LR35902Core* cpu) {
|
|||
cpu->e = 0x08;
|
||||
cpu->h = 0;
|
||||
cpu->l = 0x7C;
|
||||
gb->timer.internalDiv = 0x7A8;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
237
src/gb/mbc.c
237
src/gb/mbc.c
|
@ -79,7 +79,11 @@ static bool _isMulticart(const uint8_t* mem) {
|
|||
|
||||
void GBMBCSwitchSramBank(struct GB* gb, int bank) {
|
||||
size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM;
|
||||
GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM);
|
||||
if (bankStart + GB_SIZE_EXTERNAL_RAM > gb->sramSize) {
|
||||
mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank);
|
||||
bankStart &= (gb->sramSize - 1);
|
||||
bank = bankStart / GB_SIZE_EXTERNAL_RAM;
|
||||
}
|
||||
gb->memory.sramBank = &gb->memory.sram[bankStart];
|
||||
gb->memory.sramCurrentBank = bank;
|
||||
}
|
||||
|
@ -194,7 +198,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
case GB_MBC7:
|
||||
gb->memory.mbcWrite = _GBMBC7;
|
||||
gb->memory.mbcRead = _GBMBC7Read;
|
||||
gb->sramSize = GB_SIZE_EXTERNAL_RAM;
|
||||
gb->sramSize = 0x100;
|
||||
break;
|
||||
case GB_MMM01:
|
||||
mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01");
|
||||
|
@ -468,12 +472,25 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) {
|
|||
void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
|
||||
int bank = value & 0x7F;
|
||||
switch (address >> 13) {
|
||||
case 0x0:
|
||||
switch (value) {
|
||||
default:
|
||||
case 0:
|
||||
gb->memory.mbcState.mbc7.access = 0;
|
||||
break;
|
||||
case 0xA:
|
||||
gb->memory.mbcState.mbc7.access |= 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x1:
|
||||
GBMBCSwitchBank(gb, bank);
|
||||
break;
|
||||
case 0x2:
|
||||
if (value < 0x10) {
|
||||
GBMBCSwitchSramBank(gb, value);
|
||||
if (value == 0x40) {
|
||||
gb->memory.mbcState.mbc7.access |= 2;
|
||||
} else {
|
||||
gb->memory.mbcState.mbc7.access &= ~2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -485,17 +502,15 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
|
|||
|
||||
uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) {
|
||||
struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
|
||||
if (mbc7->access != 3) {
|
||||
return 0xFF;
|
||||
}
|
||||
switch (address & 0xF0) {
|
||||
case 0x00:
|
||||
case 0x10:
|
||||
case 0x60:
|
||||
case 0x70:
|
||||
return 0;
|
||||
case 0x20:
|
||||
if (memory->rotation && memory->rotation->readTiltX) {
|
||||
int32_t x = -memory->rotation->readTiltX(memory->rotation);
|
||||
x >>= 21;
|
||||
x += 2047;
|
||||
x += 0x81D0;
|
||||
return x;
|
||||
}
|
||||
return 0xFF;
|
||||
|
@ -503,7 +518,7 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) {
|
|||
if (memory->rotation && memory->rotation->readTiltX) {
|
||||
int32_t x = -memory->rotation->readTiltX(memory->rotation);
|
||||
x >>= 21;
|
||||
x += 2047;
|
||||
x += 0x81D0;
|
||||
return x >> 8;
|
||||
}
|
||||
return 7;
|
||||
|
@ -511,7 +526,7 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) {
|
|||
if (memory->rotation && memory->rotation->readTiltY) {
|
||||
int32_t y = -memory->rotation->readTiltY(memory->rotation);
|
||||
y >>= 21;
|
||||
y += 2047;
|
||||
y += 0x81D0;
|
||||
return y;
|
||||
}
|
||||
return 0xFF;
|
||||
|
@ -519,144 +534,142 @@ uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) {
|
|||
if (memory->rotation && memory->rotation->readTiltY) {
|
||||
int32_t y = -memory->rotation->readTiltY(memory->rotation);
|
||||
y >>= 21;
|
||||
y += 2047;
|
||||
y += 0x81D0;
|
||||
return y >> 8;
|
||||
}
|
||||
return 7;
|
||||
case 0x60:
|
||||
return 0;
|
||||
case 0x80:
|
||||
return (mbc7->sr >> 16) & 1;
|
||||
return mbc7->eeprom;
|
||||
default:
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) {
|
||||
if ((address & 0xF0) != 0x80) {
|
||||
struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
|
||||
if (mbc7->access != 3) {
|
||||
return;
|
||||
}
|
||||
struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
|
||||
GBMBC7Field old = memory->mbcState.mbc7.field;
|
||||
mbc7->field = GBMBC7FieldClearIO(value);
|
||||
if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
|
||||
if (mbc7->state == GBMBC7_STATE_WRITE) {
|
||||
if (mbc7->writable) {
|
||||
memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8;
|
||||
memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr;
|
||||
}
|
||||
mbc7->sr = 0x1FFFF;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
} else {
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
switch (address & 0xF0) {
|
||||
case 0x00:
|
||||
mbc7->latch = (value & 0x55) == 0x55;
|
||||
return;
|
||||
case 0x10:
|
||||
mbc7->latch |= (value & 0xAA);
|
||||
if (mbc7->latch == 0xAB && memory->rotation && memory->rotation->sample) {
|
||||
memory->rotation->sample(memory->rotation);
|
||||
}
|
||||
mbc7->latch = 0;
|
||||
return;
|
||||
default:
|
||||
mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value);
|
||||
return;
|
||||
case 0x80:
|
||||
break;
|
||||
}
|
||||
if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) {
|
||||
if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) {
|
||||
GBMBC7Field old = memory->mbcState.mbc7.eeprom;
|
||||
value = GBMBC7FieldFillDO(value); // Hi-Z
|
||||
if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
}
|
||||
if (!GBMBC7FieldIsCLK(old) && GBMBC7FieldIsCLK(value)) {
|
||||
if (mbc7->state == GBMBC7_STATE_READ_COMMAND || mbc7->state == GBMBC7_STATE_EEPROM_WRITE || mbc7->state == GBMBC7_STATE_EEPROM_WRAL) {
|
||||
mbc7->sr <<= 1;
|
||||
mbc7->sr |= GBMBC7FieldGetIO(value);
|
||||
mbc7->sr |= GBMBC7FieldGetDI(value);
|
||||
++mbc7->srBits;
|
||||
}
|
||||
switch (mbc7->state) {
|
||||
case GBMBC7_STATE_IDLE:
|
||||
if (GBMBC7FieldIsIO(value)) {
|
||||
if (GBMBC7FieldIsDI(value)) {
|
||||
mbc7->state = GBMBC7_STATE_READ_COMMAND;
|
||||
mbc7->srBits = 0;
|
||||
mbc7->sr = 0;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_READ_COMMAND:
|
||||
if (mbc7->srBits == 2) {
|
||||
mbc7->state = GBMBC7_STATE_READ_ADDRESS;
|
||||
mbc7->srBits = 0;
|
||||
mbc7->command = mbc7->sr;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_READ_ADDRESS:
|
||||
if (mbc7->srBits == 8) {
|
||||
mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command;
|
||||
mbc7->srBits = 0;
|
||||
mbc7->address = mbc7->sr;
|
||||
if (mbc7->state == GBMBC7_STATE_COMMAND_0) {
|
||||
switch (mbc7->address >> 6) {
|
||||
case 0:
|
||||
mbc7->writable = false;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
break;
|
||||
case 3:
|
||||
mbc7->writable = true;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
break;
|
||||
}
|
||||
if (mbc7->srBits == 10) {
|
||||
mbc7->state = 0x10 | (mbc7->sr >> 6);
|
||||
if (mbc7->state & 0xC) {
|
||||
mbc7->state &= ~0x3;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_COMMAND_0:
|
||||
if (mbc7->srBits == 16) {
|
||||
switch (mbc7->address >> 6) {
|
||||
case 0:
|
||||
mbc7->writable = false;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
break;
|
||||
case 1:
|
||||
mbc7->state = GBMBC7_STATE_WRITE;
|
||||
if (mbc7->writable) {
|
||||
int i;
|
||||
for (i = 0; i < 256; ++i) {
|
||||
memory->sramBank[i * 2] = mbc7->sr >> 8;
|
||||
memory->sramBank[i * 2 + 1] = mbc7->sr;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
mbc7->state = GBMBC7_STATE_WRITE;
|
||||
if (mbc7->writable) {
|
||||
int i;
|
||||
for (i = 0; i < 256; ++i) {
|
||||
memory->sramBank[i * 2] = 0xFF;
|
||||
memory->sramBank[i * 2 + 1] = 0xFF;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
mbc7->writable = true;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_COMMAND_SR_WRITE:
|
||||
if (mbc7->srBits == 16) {
|
||||
mbc7->srBits = 0;
|
||||
mbc7->state = GBMBC7_STATE_WRITE;
|
||||
mbc7->address = mbc7->sr & 0x7F;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_COMMAND_SR_READ:
|
||||
if (mbc7->srBits == 1) {
|
||||
mbc7->sr = memory->sramBank[mbc7->address * 2] << 8;
|
||||
mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1];
|
||||
mbc7->srBits = 0;
|
||||
mbc7->state = GBMBC7_STATE_READ;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_COMMAND_SR_FILL:
|
||||
if (mbc7->srBits == 16) {
|
||||
mbc7->sr = 0xFFFF;
|
||||
mbc7->srBits = 0;
|
||||
mbc7->state = GBMBC7_STATE_WRITE;
|
||||
case GBMBC7_STATE_DO:
|
||||
value = GBMBC7FieldSetDO(value, mbc7->sr >> 15);
|
||||
mbc7->sr <<= 1;
|
||||
--mbc7->srBits;
|
||||
if (!mbc7->srBits) {
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) {
|
||||
if (mbc7->state == GBMBC7_STATE_READ) {
|
||||
mbc7->sr <<= 1;
|
||||
++mbc7->srBits;
|
||||
switch (mbc7->state) {
|
||||
case GBMBC7_STATE_EEPROM_EWEN:
|
||||
mbc7->writable = true;
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_EWDS:
|
||||
mbc7->writable = false;
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_WRITE:
|
||||
if (mbc7->srBits == 16) {
|
||||
mbc7->srBits = 0;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
if (mbc7->writable) {
|
||||
memory->sram[mbc7->address * 2] = mbc7->sr >> 8;
|
||||
memory->sram[mbc7->address * 2 + 1] = mbc7->sr;
|
||||
}
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_ERASE:
|
||||
if (mbc7->writable) {
|
||||
memory->sram[mbc7->address * 2] = 0xFF;
|
||||
memory->sram[mbc7->address * 2 + 1] = 0xFF;
|
||||
}
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_READ:
|
||||
mbc7->srBits = 16;
|
||||
mbc7->sr = memory->sram[mbc7->address * 2] << 8;
|
||||
mbc7->sr |= memory->sram[mbc7->address * 2 + 1];
|
||||
mbc7->state = GBMBC7_STATE_DO;
|
||||
value = GBMBC7FieldClearDO(value);
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_WRAL:
|
||||
if (mbc7->srBits == 16) {
|
||||
if (mbc7->writable) {
|
||||
int i;
|
||||
for (i = 0; i < 128; ++i) {
|
||||
memory->sram[i * 2] = mbc7->sr >> 8;
|
||||
memory->sram[i * 2 + 1] = mbc7->sr;
|
||||
}
|
||||
}
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
case GBMBC7_STATE_EEPROM_ERAL:
|
||||
if (mbc7->writable) {
|
||||
int i;
|
||||
for (i = 0; i < 128; ++i) {
|
||||
memory->sram[i * 2] = 0xFF;
|
||||
memory->sram[i * 2 + 1] = 0xFF;
|
||||
}
|
||||
}
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (GBMBC7FieldIsCS(value) && GBMBC7FieldIsCLK(old) && !GBMBC7FieldIsCLK(value)) {
|
||||
value = GBMBC7FieldSetDO(value, GBMBC7FieldGetDO(old));
|
||||
}
|
||||
mbc7->eeprom = value;
|
||||
}
|
||||
|
||||
void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) {
|
||||
|
|
|
@ -437,6 +437,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) {
|
|||
if (base > 0xF100) {
|
||||
return;
|
||||
}
|
||||
mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent);
|
||||
mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8);
|
||||
if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) {
|
||||
gb->cpu->nextEvent = gb->cpu->cycles + 8;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
static const struct GBCartridgeOverride _overrides[] = {
|
||||
// None yet
|
||||
{ 0, 0, 0 }
|
||||
{ 0, 0, 0, { 0 } }
|
||||
};
|
||||
|
||||
bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverride* override) {
|
||||
|
@ -35,6 +35,12 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri
|
|||
snprintf(sectionName, sizeof(sectionName), "gb.override.%08X", override->headerCrc32);
|
||||
const char* model = ConfigurationGetValue(config, sectionName, "model");
|
||||
const char* mbc = ConfigurationGetValue(config, sectionName, "mbc");
|
||||
const char* pal[4] = {
|
||||
ConfigurationGetValue(config, sectionName, "pal[0]"),
|
||||
ConfigurationGetValue(config, sectionName, "pal[1]"),
|
||||
ConfigurationGetValue(config, sectionName, "pal[2]"),
|
||||
ConfigurationGetValue(config, sectionName, "pal[3]")
|
||||
};
|
||||
|
||||
if (model) {
|
||||
if (strcasecmp(model, "DMG") == 0) {
|
||||
|
@ -63,6 +69,21 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri
|
|||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pal[0] && pal[1] && pal[2] && pal[3]) {
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
char* end;
|
||||
unsigned long value = strtoul(pal[i], &end, 10);
|
||||
if (end == &pal[i][1] && *end == 'x') {
|
||||
value = strtoul(pal[i], &end, 16);
|
||||
}
|
||||
if (*end) {
|
||||
continue;
|
||||
}
|
||||
override->gbColors[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
@ -89,6 +110,12 @@ void GBOverrideSave(struct Configuration* config, const struct GBCartridgeOverri
|
|||
}
|
||||
ConfigurationSetValue(config, sectionName, "model", model);
|
||||
|
||||
if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) {
|
||||
ConfigurationSetIntValue(config, sectionName, "pal[0]", override->gbColors[0]);
|
||||
ConfigurationSetIntValue(config, sectionName, "pal[1]", override->gbColors[1]);
|
||||
ConfigurationSetIntValue(config, sectionName, "pal[2]", override->gbColors[2]);
|
||||
ConfigurationSetIntValue(config, sectionName, "pal[3]", override->gbColors[3]);
|
||||
}
|
||||
if (override->mbc != GB_MBC_AUTODETECT) {
|
||||
ConfigurationSetIntValue(config, sectionName, "mbc", override->mbc);
|
||||
} else {
|
||||
|
@ -105,6 +132,13 @@ void GBOverrideApply(struct GB* gb, const struct GBCartridgeOverride* override)
|
|||
gb->memory.mbcType = override->mbc;
|
||||
GBMBCInit(gb);
|
||||
}
|
||||
|
||||
if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) {
|
||||
GBVideoSetPalette(&gb->video, 0, override->gbColors[0]);
|
||||
GBVideoSetPalette(&gb->video, 1, override->gbColors[1]);
|
||||
GBVideoSetPalette(&gb->video, 2, override->gbColors[2]);
|
||||
GBVideoSetPalette(&gb->video, 3, override->gbColors[3]);
|
||||
}
|
||||
}
|
||||
|
||||
void GBOverrideApplyDefaults(struct GB* gb) {
|
||||
|
|
|
@ -67,8 +67,6 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G
|
|||
softwareRenderer->currentWy = 0;
|
||||
softwareRenderer->wx = 0;
|
||||
softwareRenderer->model = model;
|
||||
|
||||
_clearScreen(softwareRenderer);
|
||||
}
|
||||
|
||||
static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
|
||||
|
|
|
@ -169,8 +169,8 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
}
|
||||
|
||||
GBMemoryDeserialize(gb, state);
|
||||
GBIODeserialize(gb, state);
|
||||
GBVideoDeserialize(&gb->video, state);
|
||||
GBIODeserialize(gb, state);
|
||||
GBTimerDeserialize(&gb->timer, state);
|
||||
GBAudioDeserialize(&gb->audio, state);
|
||||
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gb/timer.h>
|
||||
|
||||
#include <mgba/internal/lr35902/lr35902.h>
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#include <mgba/internal/gb/serialize.h>
|
||||
|
||||
static void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate);
|
||||
|
||||
void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
|
@ -20,28 +19,28 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
GBUpdateIRQs(timer->p);
|
||||
}
|
||||
|
||||
void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBTimer* timer = context;
|
||||
timer->nextDiv += cyclesLate;
|
||||
while (timer->nextDiv > 0) {
|
||||
static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) {
|
||||
while (timer->nextDiv >= GB_DMG_DIV_PERIOD) {
|
||||
timer->nextDiv -= GB_DMG_DIV_PERIOD;
|
||||
|
||||
// Make sure to trigger when the correct bit is a falling edge
|
||||
if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) {
|
||||
++timer->p->memory.io[REG_TIMA];
|
||||
if (!timer->p->memory.io[REG_TIMA]) {
|
||||
mTimingSchedule(timing, &timer->irq, 4 - cyclesLate);
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, 4 - cyclesLate);
|
||||
}
|
||||
}
|
||||
++timer->internalDiv;
|
||||
timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4;
|
||||
}
|
||||
_GBTimerUpdateDIV(timer, cyclesLate);
|
||||
}
|
||||
|
||||
void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate) {
|
||||
void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBTimer* timer = context;
|
||||
timer->nextDiv += cyclesLate;
|
||||
_GBTimerDivIncrement(timer, cyclesLate);
|
||||
// Batch div increments
|
||||
int divsToGo = 16 - (timer->internalDiv & 15) + (timer->nextDiv / GB_DMG_DIV_PERIOD);
|
||||
int divsToGo = 16 - (timer->internalDiv & 15);
|
||||
int timaToGo = INT_MAX;
|
||||
if (timer->timaPeriod) {
|
||||
timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1));
|
||||
|
@ -49,18 +48,14 @@ void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate) {
|
|||
if (timaToGo < divsToGo) {
|
||||
divsToGo = timaToGo;
|
||||
}
|
||||
if (divsToGo > 16) {
|
||||
divsToGo = 16;
|
||||
}
|
||||
timer->nextDiv &= GB_DMG_DIV_PERIOD - 1;
|
||||
timer->nextDiv += GB_DMG_DIV_PERIOD * divsToGo;
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - cyclesLate);
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo;
|
||||
mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate);
|
||||
}
|
||||
|
||||
void GBTimerReset(struct GBTimer* timer) {
|
||||
timer->event.context = timer;
|
||||
timer->event.name = "GB Timer";
|
||||
timer->event.callback = _GBTimerIncrement;
|
||||
timer->event.callback = _GBTimerUpdate;
|
||||
timer->event.priority = 0x20;
|
||||
timer->irq.context = timer;
|
||||
timer->irq.name = "GB Timer IRQ";
|
||||
|
@ -73,11 +68,19 @@ void GBTimerReset(struct GBTimer* timer) {
|
|||
}
|
||||
|
||||
void GBTimerDivReset(struct GBTimer* timer) {
|
||||
timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
_GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3);
|
||||
if (timer->internalDiv & (timer->timaPeriod >> 1)) {
|
||||
++timer->p->memory.io[REG_TIMA];
|
||||
if (!timer->p->memory.io[REG_TIMA]) {
|
||||
mTimingSchedule(&timer->p->timing, &timer->irq, 4 - ((timer->p->cpu->executionState + 1) & 3));
|
||||
}
|
||||
}
|
||||
timer->p->memory.io[REG_DIV] = 0;
|
||||
timer->internalDiv = 0;
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD;
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3));
|
||||
}
|
||||
|
||||
uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
||||
|
@ -96,11 +99,15 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
|||
timer->timaPeriod = 256 >> 4;
|
||||
break;
|
||||
}
|
||||
|
||||
timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
_GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3);
|
||||
timer->nextDiv += GB_DMG_DIV_PERIOD;
|
||||
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
|
||||
} else {
|
||||
timer->timaPeriod = 0;
|
||||
}
|
||||
mTimingDeschedule(&timer->p->timing, &timer->event);
|
||||
_GBTimerUpdateDIV(timer, 0);
|
||||
return tac;
|
||||
}
|
||||
|
||||
|
|
|
@ -185,9 +185,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
|||
next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7);
|
||||
video->mode = 2;
|
||||
video->modeEvent.callback = _endMode2;
|
||||
if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) {
|
||||
video->p->memory.rotation->sample(video->p->memory.rotation);
|
||||
}
|
||||
} else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) {
|
||||
video->p->memory.io[REG_LY] = 0;
|
||||
next = GB_VIDEO_HORIZONTAL_LENGTH - 8;
|
||||
|
@ -389,6 +386,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value) {
|
|||
video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
|
||||
GBUpdateIRQs(video->p);
|
||||
}
|
||||
video->p->memory.io[REG_STAT] = video->stat;
|
||||
}
|
||||
|
||||
void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) {
|
||||
|
@ -471,11 +469,11 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) {
|
|||
video->vramCurrentBank = value;
|
||||
}
|
||||
|
||||
void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) {
|
||||
void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color) {
|
||||
if (index >= 4) {
|
||||
return;
|
||||
}
|
||||
video->dmgPalette[index] = color;
|
||||
video->dmgPalette[index] = M_RGB8_TO_RGB5(color);
|
||||
}
|
||||
|
||||
static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include <mgba/internal/gba/input.h>
|
||||
#endif
|
||||
|
||||
const static struct mCoreChannelInfo _GBAVideoLayers[] = {
|
||||
static const struct mCoreChannelInfo _GBAVideoLayers[] = {
|
||||
{ 0, "bg0", "Background 0", NULL },
|
||||
{ 1, "bg1", "Background 1", NULL },
|
||||
{ 2, "bg2", "Background 2", NULL },
|
||||
|
@ -36,16 +36,16 @@ const static struct mCoreChannelInfo _GBAVideoLayers[] = {
|
|||
{ 4, "obj", "Objects", NULL },
|
||||
};
|
||||
|
||||
const static struct mCoreChannelInfo _GBAAudioChannels[] = {
|
||||
{ 0, "ch0", "PSG Channel 0", "Square/Sweep" },
|
||||
{ 1, "ch1", "PSG Channel 1", "Square" },
|
||||
{ 2, "ch2", "PSG Channel 2", "PCM" },
|
||||
{ 3, "ch3", "PSG Channel 3", "Noise" },
|
||||
static const struct mCoreChannelInfo _GBAAudioChannels[] = {
|
||||
{ 0, "ch1", "PSG Channel 1", "Square/Sweep" },
|
||||
{ 1, "ch2", "PSG Channel 2", "Square" },
|
||||
{ 2, "ch3", "PSG Channel 3", "PCM" },
|
||||
{ 3, "ch4", "PSG Channel 4", "Noise" },
|
||||
{ 4, "chA", "FIFO Channel A", NULL },
|
||||
{ 5, "chB", "FIFO Channel B", NULL },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = {
|
||||
static const struct mCoreMemoryBlock _GBAMemoryBlocks[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
|
||||
{ REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
{ REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
@ -59,7 +59,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = {
|
|||
{ REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = {
|
||||
static const struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
|
||||
{ REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
{ REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
@ -74,7 +74,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = {
|
|||
{ REGION_CART_SRAM, "sram", "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_SRAM, SIZE_CART_SRAM, true },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = {
|
||||
static const struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
|
||||
{ REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
{ REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
@ -89,7 +89,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = {
|
|||
{ REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH512, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = {
|
||||
static const struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
|
||||
{ REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
{ REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
@ -104,7 +104,7 @@ const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = {
|
|||
{ REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
|
||||
};
|
||||
|
||||
const static struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
|
||||
static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
|
||||
{ -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
|
||||
{ REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
|
||||
{ REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
|
|
30
src/gba/io.c
30
src/gba/io.c
|
@ -501,23 +501,19 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
|
||||
case REG_TM0CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerUpdateRegister(gba, 0);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[0], &gba->timing, gba->cpu, &gba->memory.io[REG_TM0CNT_LO >> 1], value);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[0], &gba->timing, &gba->memory.io[REG_TM0CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM1CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerUpdateRegister(gba, 1);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[1], &gba->timing, gba->cpu, &gba->memory.io[REG_TM1CNT_LO >> 1], value);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[1], &gba->timing, &gba->memory.io[REG_TM1CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM2CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerUpdateRegister(gba, 2);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[2], &gba->timing, gba->cpu, &gba->memory.io[REG_TM2CNT_LO >> 1], value);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[2], &gba->timing, &gba->memory.io[REG_TM2CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM3CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerUpdateRegister(gba, 3);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[3], &gba->timing, gba->cpu, &gba->memory.io[REG_TM3CNT_LO >> 1], value);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[3], &gba->timing, &gba->memory.io[REG_TM3CNT_LO >> 1], value);
|
||||
break;
|
||||
|
||||
// SIO
|
||||
|
@ -711,17 +707,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
}
|
||||
|
||||
switch (address) {
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
case REG_TM0CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 0);
|
||||
GBATimerUpdateRegister(gba, 0, 2);
|
||||
break;
|
||||
case REG_TM1CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 1);
|
||||
GBATimerUpdateRegister(gba, 1, 2);
|
||||
break;
|
||||
case REG_TM2CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 2);
|
||||
GBATimerUpdateRegister(gba, 2, 2);
|
||||
break;
|
||||
case REG_TM3CNT_LO:
|
||||
GBATimerUpdateRegister(gba, 3);
|
||||
GBATimerUpdateRegister(gba, 3, 2);
|
||||
break;
|
||||
|
||||
case REG_KEYINPUT:
|
||||
|
@ -929,10 +926,9 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
for (i = 0; i < 4; ++i) {
|
||||
STORE_16(gba->memory.io[(REG_DMA0CNT_LO + i * 12) >> 1], (REG_DMA0CNT_LO + i * 12), state->io);
|
||||
STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload);
|
||||
STORE_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
|
||||
STORE_32(gba->timers[i].lastEvent - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].lastEvent);
|
||||
STORE_32(gba->timers[i].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent);
|
||||
STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);
|
||||
STORE_32(gba->timers[i].irq.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextIrq);
|
||||
STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags);
|
||||
STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
|
||||
|
@ -958,8 +954,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
uint32_t when;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload);
|
||||
LOAD_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
|
||||
LOAD_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);
|
||||
LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags);
|
||||
if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) {
|
||||
// Overwrite invalid values in savestate
|
||||
|
@ -972,6 +966,10 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
if (GBATimerFlagsIsEnable(gba->timers[i].flags)) {
|
||||
mTimingSchedule(&gba->timing, &gba->timers[i].event, when);
|
||||
}
|
||||
LOAD_32(when, 0, &state->timers[i].nextIrq);
|
||||
if (GBATimerFlagsIsIrqPending(gba->timers[i].flags)) {
|
||||
mTimingSchedule(&gba->timing, &gba->timers[i].irq, when);
|
||||
}
|
||||
|
||||
LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io);
|
||||
LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
|
|
|
@ -115,24 +115,14 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
|
|||
|
||||
uint32_t copySize = size - 0x1C;
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_SRAM:
|
||||
if (copySize > SIZE_CART_SRAM) {
|
||||
copySize = SIZE_CART_SRAM;
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FLASH512:
|
||||
if (copySize > SIZE_CART_FLASH512) {
|
||||
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming);
|
||||
}
|
||||
// Fall through
|
||||
case SAVEDATA_FLASH1M:
|
||||
if (copySize > SIZE_CART_FLASH1M) {
|
||||
copySize = SIZE_CART_FLASH1M;
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_EEPROM:
|
||||
if (copySize > SIZE_CART_EEPROM) {
|
||||
copySize = SAVEDATA_EEPROM;
|
||||
default:
|
||||
if (copySize > GBASavedataSize(&gba->memory.savedata)) {
|
||||
copySize = GBASavedataSize(&gba->memory.savedata);
|
||||
}
|
||||
break;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
|
@ -141,6 +131,7 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
|
|||
}
|
||||
|
||||
memcpy(gba->memory.savedata.data, &payload[0x1C], copySize);
|
||||
gba->memory.savedata.vf && gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size);
|
||||
|
||||
free(payload);
|
||||
return true;
|
||||
|
|
155
src/gba/timer.c
155
src/gba/timer.c
|
@ -8,6 +8,58 @@
|
|||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
|
||||
#define TIMER_IRQ_DELAY 7
|
||||
#define TIMER_RELOAD_DELAY 0
|
||||
#define TIMER_STARTUP_DELAY 2
|
||||
|
||||
static void GBATimerIrq(struct GBA* gba, int timerId) {
|
||||
struct GBATimer* timer = &gba->timers[timerId];
|
||||
if (GBATimerFlagsIsIrqPending(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsClearIrqPending(timer->flags);
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 0);
|
||||
}
|
||||
|
||||
static void GBATimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 1);
|
||||
}
|
||||
|
||||
static void GBATimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 2);
|
||||
}
|
||||
|
||||
static void GBATimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 3);
|
||||
}
|
||||
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) {
|
||||
*io = timer->reload;
|
||||
int32_t currentTime = mTimingCurrentTime(timing) - cyclesLate;
|
||||
int32_t tickMask = (1 << GBATimerFlagsGetPrescaleBits(timer->flags)) - 1;
|
||||
currentTime &= ~tickMask;
|
||||
timer->lastEvent = currentTime;
|
||||
GBATimerUpdateRegisterInternal(timer, timing, io, 0);
|
||||
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsFillIrqPending(timer->flags);
|
||||
if (!mTimingIsScheduled(timing, &timer->irq)) {
|
||||
mTimingSchedule(timing, &timer->irq, TIMER_IRQ_DELAY - cyclesLate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
||||
if (!gba->audio.enable) {
|
||||
return;
|
||||
|
@ -25,28 +77,13 @@ void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, u
|
|||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
|
||||
++*io;
|
||||
if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
||||
mTimingSchedule(timing, &nextTimer->event, -cyclesLate);
|
||||
GBATimerUpdate(timing, nextTimer, io, cyclesLate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) {
|
||||
*io = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
timer->lastEvent = timing->masterCycles - cyclesLate;
|
||||
|
||||
if (!GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
uint32_t nextEvent = timer->overflowInterval - cyclesLate;
|
||||
mTimingSchedule(timing, &timer->event, nextEvent);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[0];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0);
|
||||
}
|
||||
GBATimerUpdateAudio(gba, 0, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
|
@ -54,10 +91,6 @@ static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cycl
|
|||
|
||||
static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[1];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER1);
|
||||
}
|
||||
GBATimerUpdateAudio(gba, 1, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
|
@ -65,20 +98,12 @@ static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cycl
|
|||
|
||||
static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[2];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER2);
|
||||
}
|
||||
GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[3];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER3);
|
||||
}
|
||||
GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
|
@ -100,63 +125,97 @@ void GBATimerInit(struct GBA* gba) {
|
|||
gba->timers[3].event.callback = GBATimerUpdate3;
|
||||
gba->timers[3].event.context = gba;
|
||||
gba->timers[3].event.priority = 0x23;
|
||||
gba->timers[0].irq.name = "GBA Timer 0 IRQ";
|
||||
gba->timers[0].irq.callback = GBATimerIrq0;
|
||||
gba->timers[0].irq.context = gba;
|
||||
gba->timers[0].irq.priority = 0x28;
|
||||
gba->timers[1].irq.name = "GBA Timer 1 IRQ";
|
||||
gba->timers[1].irq.callback = GBATimerIrq1;
|
||||
gba->timers[1].irq.context = gba;
|
||||
gba->timers[1].irq.priority = 0x29;
|
||||
gba->timers[2].irq.name = "GBA Timer 2 IRQ";
|
||||
gba->timers[2].irq.callback = GBATimerIrq2;
|
||||
gba->timers[2].irq.context = gba;
|
||||
gba->timers[2].irq.priority = 0x2A;
|
||||
gba->timers[3].irq.name = "GBA Timer 3 IRQ";
|
||||
gba->timers[3].irq.callback = GBATimerIrq3;
|
||||
gba->timers[3].irq.context = gba;
|
||||
gba->timers[3].irq.priority = 0x2B;
|
||||
}
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer) {
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) {
|
||||
struct GBATimer* currentTimer = &gba->timers[timer];
|
||||
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
int32_t prefetchSkew = -2;
|
||||
int32_t prefetchSkew = cyclesLate;
|
||||
if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) {
|
||||
prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB;
|
||||
}
|
||||
GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, gba->cpu, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew);
|
||||
GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew);
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew) {
|
||||
int32_t diff = cpu->cycles - (timer->lastEvent - timing->masterCycles);
|
||||
*io = timer->oldReload + ((diff + skew) >> GBATimerFlagsGetPrescaleBits(timer->flags));
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew) {
|
||||
if (!GBATimerFlagsIsEnable(timer->flags) || GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int prescaleBits = GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
int32_t currentTime = mTimingCurrentTime(timing) - skew;
|
||||
int32_t tickMask = (1 << prescaleBits) - 1;
|
||||
currentTime &= ~tickMask;
|
||||
int32_t tickIncrement = currentTime - timer->lastEvent;
|
||||
timer->lastEvent = currentTime;
|
||||
tickIncrement >>= prescaleBits;
|
||||
tickIncrement += *io;
|
||||
*io = tickIncrement;
|
||||
if (!mTimingIsScheduled(timing, &timer->event)) {
|
||||
tickIncrement = (0x10000 - tickIncrement) << prescaleBits;
|
||||
currentTime -= mTimingCurrentTime(timing) - skew;
|
||||
mTimingSchedule(timing, &timer->event, TIMER_RELOAD_DELAY + tickIncrement + currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) {
|
||||
timer->reload = reload;
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control) {
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t control) {
|
||||
GBATimerUpdateRegisterInternal(timer, timing, io, 0);
|
||||
|
||||
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
unsigned prescaleBits;
|
||||
switch (control & 0x0003) {
|
||||
case 0x0000:
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, timer->forcedPrescale);
|
||||
prescaleBits = 0;
|
||||
break;
|
||||
case 0x0001:
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 6 + timer->forcedPrescale);
|
||||
prescaleBits = 6;
|
||||
break;
|
||||
case 0x0002:
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 8 + timer->forcedPrescale);
|
||||
prescaleBits = 8;
|
||||
break;
|
||||
case 0x0003:
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 10 + timer->forcedPrescale);
|
||||
prescaleBits = 10;
|
||||
break;
|
||||
}
|
||||
prescaleBits += timer->forcedPrescale;
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, prescaleBits);
|
||||
timer->flags = GBATimerFlagsTestFillCountUp(timer->flags, timer > 0 && (control & 0x0004));
|
||||
timer->flags = GBATimerFlagsTestFillDoIrq(timer->flags, control & 0x0040);
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
bool wasEnabled = GBATimerFlagsIsEnable(timer->flags);
|
||||
timer->flags = GBATimerFlagsTestFillEnable(timer->flags, control & 0x0080);
|
||||
if (!wasEnabled && GBATimerFlagsIsEnable(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
if (!GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
mTimingSchedule(timing, &timer->event, timer->overflowInterval);
|
||||
}
|
||||
*io = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
timer->lastEvent = timing->masterCycles + cpu->cycles;
|
||||
int32_t tickMask = (1 << prescaleBits) - 1;
|
||||
timer->lastEvent = (mTimingCurrentTime(timing) - TIMER_STARTUP_DELAY) & ~tickMask;
|
||||
GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_STARTUP_DELAY);
|
||||
} else if (wasEnabled && !GBATimerFlagsIsEnable(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
} else if (GBATimerFlagsIsEnable(timer->flags) && GBATimerFlagsGetPrescaleBits(timer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
mTimingSchedule(timing, &timer->event, timer->overflowInterval - timer->lastEvent);
|
||||
int32_t tickMask = (1 << prescaleBits) - 1;
|
||||
timer->lastEvent = (mTimingCurrentTime(timing) - TIMER_STARTUP_DELAY) & ~tickMask;
|
||||
GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_STARTUP_DELAY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -315,24 +315,24 @@ DEFINE_POPPUSH_DECODER_LR35902(HL);
|
|||
DEFINE_POPPUSH_DECODER_LR35902(AF);
|
||||
|
||||
#define DEFINE_CB_2_DECODER_LR35902(NAME, BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## B, info->op1.reg = LR35902_REG_B; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## C, info->op1.reg = LR35902_REG_C; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## D, info->op1.reg = LR35902_REG_D; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## E, info->op1.reg = LR35902_REG_E; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## H, info->op1.reg = LR35902_REG_H; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## L, info->op1.reg = LR35902_REG_L; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## HL, info->op1.reg = LR35902_REG_HL; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## A, info->op1.reg = LR35902_REG_A; BODY)
|
||||
DEFINE_DECODER_LR35902(NAME ## B, info->op2.reg = LR35902_REG_B; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## C, info->op2.reg = LR35902_REG_C; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## D, info->op2.reg = LR35902_REG_D; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## E, info->op2.reg = LR35902_REG_E; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## H, info->op2.reg = LR35902_REG_H; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## L, info->op2.reg = LR35902_REG_L; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## HL, info->op2.reg = LR35902_REG_HL; info->op2.flags = LR35902_OP_FLAG_MEMORY; BODY) \
|
||||
DEFINE_DECODER_LR35902(NAME ## A, info->op2.reg = LR35902_REG_A; BODY)
|
||||
|
||||
#define DEFINE_CB_DECODER_LR35902(NAME, BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 0, info->op2.immediate = 1; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 1, info->op2.immediate = 2; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 2, info->op2.immediate = 4; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 3, info->op2.immediate = 8; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 4, info->op2.immediate = 16; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 5, info->op2.immediate = 32; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 6, info->op2.immediate = 64; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 7, info->op2.immediate = 128; BODY)
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 0, info->op1.immediate = 0; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 1, info->op1.immediate = 1; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 2, info->op1.immediate = 2; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 3, info->op1.immediate = 3; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 4, info->op1.immediate = 4; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 5, info->op1.immediate = 5; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 6, info->op1.immediate = 6; BODY) \
|
||||
DEFINE_CB_2_DECODER_LR35902(NAME ## 7, info->op1.immediate = 7; BODY)
|
||||
|
||||
DEFINE_CB_DECODER_LR35902(BIT, info->mnemonic = LR35902_MN_BIT)
|
||||
DEFINE_CB_DECODER_LR35902(RES, info->mnemonic = LR35902_MN_RES)
|
||||
|
@ -496,7 +496,7 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) {
|
|||
}
|
||||
|
||||
if (op.flags & LR35902_OP_FLAG_MEMORY) {
|
||||
strncpy(buffer, "(", blen - 1);
|
||||
strncpy(buffer, "[", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
if (op.reg) {
|
||||
|
@ -519,7 +519,7 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) {
|
|||
ADVANCE(1);
|
||||
}
|
||||
if (op.flags & LR35902_OP_FLAG_MEMORY) {
|
||||
strncpy(buffer, ")", blen - 1);
|
||||
strncpy(buffer, "]", blen - 1);
|
||||
ADVANCE(1);
|
||||
}
|
||||
return total;
|
||||
|
@ -544,7 +544,7 @@ int LR35902Disassemble(struct LR35902InstructionInfo* info, char* buffer, int bl
|
|||
}
|
||||
}
|
||||
|
||||
if (info->op1.reg || info->op1.immediate) {
|
||||
if (info->op1.reg || info->op1.immediate || info->op2.reg || info->op2.immediate) {
|
||||
written = _decodeOperand(info->op1, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
|
|||
drawW -= drawW % v->width;
|
||||
drawH -= drawH % v->height;
|
||||
}
|
||||
glViewport(0, 0, v->width, v->height);
|
||||
glViewport(0, 0, w, h);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
|
@ -203,13 +203,13 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
int drawH = shader->height;
|
||||
int padW = 0;
|
||||
int padH = 0;
|
||||
if (!shader->width) {
|
||||
if (!drawW) {
|
||||
drawW = viewport[2];
|
||||
padW = viewport[0];
|
||||
} else if (shader->width < 0) {
|
||||
drawW = context->d.width * -shader->width;
|
||||
}
|
||||
if (!shader->height) {
|
||||
if (!drawH) {
|
||||
drawH = viewport[3];
|
||||
padH = viewport[1];
|
||||
} else if (shader->height < 0) {
|
||||
|
@ -234,7 +234,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glUseProgram(shader->program);
|
||||
glUniform1i(shader->texLocation, 0);
|
||||
glUniform2f(shader->texSizeLocation, context->d.width, context->d.height);
|
||||
glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH);
|
||||
glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
|
||||
glEnableVertexAttribArray(shader->positionLocation);
|
||||
size_t u;
|
||||
|
@ -290,7 +290,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
}
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glBindTexture(GL_TEXTURE_2D, shader->tex);
|
||||
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
||||
}
|
||||
|
||||
void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
||||
|
@ -298,12 +297,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
|||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
|
||||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
context->finalShader.filter = v->filter;
|
||||
_drawShader(context, &context->initialShader);
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
glViewport(0, 0, viewport[2], viewport[3]);
|
||||
_drawShader(context, &context->shaders[n]);
|
||||
}
|
||||
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
|
||||
_drawShader(context, &context->finalShader);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glUseProgram(0);
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
find_program(FIXUP vita-elf-create)
|
||||
find_program(MAKE_FSELF vita-make-fself)
|
||||
find_program(MAKE_SFO vita-mksfoex)
|
||||
include("${VITASDK}/share/vita.cmake" REQUIRED)
|
||||
|
||||
find_program(OBJCOPY ${cross_prefix}objcopy)
|
||||
find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share)
|
||||
find_program(STRIP ${cross_prefix}strip)
|
||||
|
||||
set(OS_DEFINES IOAPI_NO_64)
|
||||
set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE)
|
||||
|
@ -37,34 +35,16 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o
|
|||
COMMAND ${OBJCOPY_CMD} backdrop.png ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_custom_target(${BINARY_NAME}.velf ALL
|
||||
${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf
|
||||
COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB}
|
||||
DEPENDS ${BINARY_NAME}.elf)
|
||||
|
||||
add_custom_target(sce_sys ${CMAKE_COMMAND} -E make_directory sce_sys)
|
||||
|
||||
add_custom_target(param.sfo
|
||||
${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA00001 sce_sys/param.sfo
|
||||
DEPENDS sce_sys)
|
||||
|
||||
add_custom_target(eboot.bin
|
||||
${MAKE_FSELF} -s ${BINARY_NAME}.velf eboot.bin
|
||||
DEPENDS ${BINARY_NAME}.velf)
|
||||
vita_create_self(${BINARY_NAME}.self ${BINARY_NAME}.elf)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/template.xml.in ${CMAKE_CURRENT_BINARY_DIR}/template.xml)
|
||||
|
||||
add_custom_target(livearea
|
||||
${CMAKE_COMMAND} -E make_directory sce_sys/livearea/contents
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/icon0.png sce_sys
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/pic0.png sce_sys
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bg.png sce_sys/livearea/contents
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents
|
||||
DEPENDS sce_sys)
|
||||
|
||||
add_custom_target(${BINARY_NAME}.vpk ALL
|
||||
zip -qr ${BINARY_NAME}.vpk sce_sys eboot.bin
|
||||
DEPENDS livearea eboot.bin param.sfo)
|
||||
vita_create_vpk(${BINARY_NAME}.vpk MGBA00001 ${BINARY_NAME}.self
|
||||
NAME ${PROJECT_NAME}
|
||||
FILE ${CMAKE_CURRENT_SOURCE_DIR}/icon0.png sce_sys/icon0.png
|
||||
FILE ${CMAKE_CURRENT_SOURCE_DIR}/pic0.png sce_sys/pic0.png
|
||||
FILE ${CMAKE_CURRENT_SOURCE_DIR}/bg.png sce_sys/livearea/contents/bg.png
|
||||
FILE ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents/startup.png
|
||||
FILE ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents/template.xml)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2)
|
||||
|
|
|
@ -14,7 +14,7 @@ set(CMAKE_PROGRAM_PATH ${toolchain_dir}/bin)
|
|||
|
||||
set(cross_prefix arm-vita-eabi-)
|
||||
set(inc_flags -I${toolchain_dir}/include)
|
||||
set(link_flags "-L${toolchain_dir}/lib -Wl,-q")
|
||||
set(link_flags "-L${toolchain_dir}/lib -Wl,-z,nocopyreloc")
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name")
|
||||
set(CMAKE_SYSTEM_PROCESSOR armv7-a CACHE INTERNAL "processor")
|
||||
|
@ -26,9 +26,9 @@ find_program(CMAKE_C_COMPILER ${cross_prefix}gcc${extension})
|
|||
find_program(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension})
|
||||
find_program(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension})
|
||||
find_program(CMAKE_LINKER ${cross_prefix}ld${extension})
|
||||
set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags")
|
||||
set(CMAKE_C_FLAGS "${inc_flags} -Wl,-q" CACHE INTERNAL "c compiler flags")
|
||||
set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "assembler flags")
|
||||
set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags")
|
||||
set(CMAKE_CXX_FLAGS "${inc_flags} -Wl,-q" CACHE INTERNAL "cxx compiler flags")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags")
|
||||
|
@ -41,6 +41,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE INTERNAL "")
|
|||
set(ENV{PKG_CONFIG_PATH} ${VITASDK}/arm-vita-eabi/lib/pkgconfig)
|
||||
set(ENV{PKG_CONFIG_LIBDIR} ${VITASDK}/arm-vita-eabi/lib/pkgconfig)
|
||||
|
||||
set(VITA_ELF_CREATE "${VITASDK}/bin/vita-elf-create${TOOL_OS_SUFFIX}" CACHE PATH "vita-elf-create")
|
||||
set(VITA_ELF_EXPORT "${VITASDK}/bin/vita-elf-export${TOOL_OS_SUFFIX}" CACHE PATH "vita-elf-export")
|
||||
set(VITA_LIBS_GEN "${VITASDK}/bin/vita-libs-gen${TOOL_OS_SUFFIX}" CACHE PATH "vita-libs-gen")
|
||||
set(VITA_MAKE_FSELF "${VITASDK}/bin/vita-make-fself${TOOL_OS_SUFFIX}" CACHE PATH "vita-make-fself")
|
||||
set(VITA_MKSFOEX "${VITASDK}/bin/vita-mksfoex${TOOL_OS_SUFFIX}" CACHE PATH "vita-mksfoex")
|
||||
set(VITA_PACK_VPK "${VITASDK}/bin/vita-pack-vpk${TOOL_OS_SUFFIX}" CACHE PATH "vita-pack-vpk")
|
||||
|
||||
set(PSP2 ON)
|
||||
add_definitions(-DPSP2)
|
||||
set(M_LIBRARY m)
|
||||
|
|
|
@ -30,6 +30,7 @@ void free(void*);
|
|||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/mem-search.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
#include <mgba/core/version.h>
|
||||
|
||||
#define PYEXPORT extern "Python+C"
|
||||
#include "platform/python/log.h"
|
||||
|
|
|
@ -23,6 +23,7 @@ ffi.set_source("mgba._pylib", """
|
|||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/mem-search.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/input.h>
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from ._pylib import ffi, lib
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
def createCallback(structName, cbName, funcName=None):
|
||||
funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:])
|
||||
fullStruct = "struct {}*".format(structName)
|
||||
|
@ -13,3 +15,19 @@ def createCallback(structName, cbName, funcName=None):
|
|||
return getattr(ffi.from_handle(h.pyobj), cbName)(*args)
|
||||
|
||||
return ffi.def_extern(name=funcName)(cb)
|
||||
|
||||
version = ffi.string(lib.projectVersion).decode('utf-8')
|
||||
|
||||
GitInfo = namedtuple("GitInfo", "commit commitShort branch revision")
|
||||
|
||||
git = {}
|
||||
if lib.gitCommit and lib.gitCommit != "(unknown)":
|
||||
git['commit'] = ffi.string(lib.gitCommit).decode('utf-8')
|
||||
if lib.gitCommitShort and lib.gitCommitShort != "(unknown)":
|
||||
git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8')
|
||||
if lib.gitBranch and lib.gitBranch != "(unknown)":
|
||||
git['branch'] = ffi.string(lib.gitBranch).decode('utf-8')
|
||||
if lib.gitRevision > 0:
|
||||
git['revision'] = lib.gitRevision
|
||||
|
||||
git = GitInfo(**git)
|
||||
|
|
|
@ -35,6 +35,10 @@ class GB(Core):
|
|||
self._native.video.renderer.cache = ffi.NULL
|
||||
lib.mTileCacheDeinit(cache)
|
||||
|
||||
def reset(self):
|
||||
super(GB, self).reset()
|
||||
self.memory = GBMemory(self._core)
|
||||
|
||||
def attachSIO(self, link):
|
||||
lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native)
|
||||
|
||||
|
@ -61,21 +65,46 @@ class GBSIODriver(object):
|
|||
return value
|
||||
|
||||
class GBSIOSimpleDriver(GBSIODriver):
|
||||
def __init__(self):
|
||||
def __init__(self, period=0x100):
|
||||
super(GBSIOSimpleDriver, self).__init__()
|
||||
self.tx = 0xFF
|
||||
self.rx = 0xFF
|
||||
self.rx = 0x00
|
||||
self._period = period
|
||||
|
||||
def init(self):
|
||||
self._native.p.period = self._period
|
||||
return True
|
||||
|
||||
def writeSB(self, value):
|
||||
self.rx = value
|
||||
|
||||
def schedule(self, period=0x100, when=0):
|
||||
def writeSC(self, value):
|
||||
self._native.p.period = self._period
|
||||
if value & 0x80:
|
||||
lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event))
|
||||
lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period)
|
||||
return value
|
||||
|
||||
def isReady(self):
|
||||
return not self._native.p.remainingBits
|
||||
|
||||
@property
|
||||
def tx(self):
|
||||
self._native.p.pendingSB
|
||||
|
||||
@property
|
||||
def period(self):
|
||||
return self._native.p.period
|
||||
|
||||
@tx.setter
|
||||
def tx(self, newTx):
|
||||
self._native.p.pendingSB = newTx
|
||||
self._native.p.remainingBits = 8
|
||||
self._native.p.period = period
|
||||
self._native.p.pendingSB = self.tx
|
||||
self.tx = 0xFF
|
||||
lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event))
|
||||
lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), when)
|
||||
|
||||
@period.setter
|
||||
def period(self, newPeriod):
|
||||
self._period = newPeriod
|
||||
if self._native.p:
|
||||
self._native.p.period = newPeriod
|
||||
|
||||
class GBMemory(Memory):
|
||||
def __init__(self, core):
|
||||
|
|
|
@ -6,64 +6,74 @@
|
|||
from ._pylib import ffi, lib
|
||||
from . import png
|
||||
|
||||
try:
|
||||
import PIL.Image as PImage
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Image:
|
||||
def __init__(self, width, height, stride=0):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.stride = stride
|
||||
self.constitute()
|
||||
def __init__(self, width, height, stride=0):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.stride = stride
|
||||
self.constitute()
|
||||
|
||||
def constitute(self):
|
||||
if self.stride <= 0:
|
||||
self.stride = self.width
|
||||
self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height))
|
||||
def constitute(self):
|
||||
if self.stride <= 0:
|
||||
self.stride = self.width
|
||||
self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height))
|
||||
|
||||
def savePNG(self, f):
|
||||
p = png.PNG(f)
|
||||
success = p.writeHeader(self)
|
||||
success = success and p.writePixels(self)
|
||||
p.writeClose()
|
||||
return success
|
||||
def savePNG(self, f):
|
||||
p = png.PNG(f)
|
||||
success = p.writeHeader(self)
|
||||
success = success and p.writePixels(self)
|
||||
p.writeClose()
|
||||
return success
|
||||
|
||||
if 'PImage' in globals():
|
||||
def toPIL(self):
|
||||
return PImage.frombytes("RGBX", (self.width, self.height), ffi.buffer(self.buffer), "raw",
|
||||
"RGBX", self.stride * 4)
|
||||
|
||||
def u16ToU32(c):
|
||||
r = c & 0x1F
|
||||
g = (c >> 5) & 0x1F
|
||||
b = (c >> 10) & 0x1F
|
||||
a = (c >> 15) & 1
|
||||
abgr = r << 3
|
||||
abgr |= g << 11
|
||||
abgr |= b << 19
|
||||
abgr |= (a * 0xFF) << 24
|
||||
return abgr
|
||||
r = c & 0x1F
|
||||
g = (c >> 5) & 0x1F
|
||||
b = (c >> 10) & 0x1F
|
||||
a = (c >> 15) & 1
|
||||
abgr = r << 3
|
||||
abgr |= g << 11
|
||||
abgr |= b << 19
|
||||
abgr |= (a * 0xFF) << 24
|
||||
return abgr
|
||||
|
||||
def u32ToU16(c):
|
||||
r = (c >> 3) & 0x1F
|
||||
g = (c >> 11) & 0x1F
|
||||
b = (c >> 19) & 0x1F
|
||||
a = c >> 31
|
||||
abgr = r
|
||||
abgr |= g << 5
|
||||
abgr |= b << 10
|
||||
abgr |= a << 15
|
||||
return abgr
|
||||
r = (c >> 3) & 0x1F
|
||||
g = (c >> 11) & 0x1F
|
||||
b = (c >> 19) & 0x1F
|
||||
a = c >> 31
|
||||
abgr = r
|
||||
abgr |= g << 5
|
||||
abgr |= b << 10
|
||||
abgr |= a << 15
|
||||
return abgr
|
||||
|
||||
if ffi.sizeof("color_t") == 2:
|
||||
def colorToU16(c):
|
||||
return c
|
||||
def colorToU16(c):
|
||||
return c
|
||||
|
||||
colorToU32 = u16ToU32
|
||||
colorToU32 = u16ToU32
|
||||
|
||||
def u16ToColor(c):
|
||||
return c
|
||||
def u16ToColor(c):
|
||||
return c
|
||||
|
||||
u32ToColor = u32ToU16
|
||||
u32ToColor = u32ToU16
|
||||
else:
|
||||
def colorToU32(c):
|
||||
return c
|
||||
def colorToU32(c):
|
||||
return c
|
||||
|
||||
colorToU16 = u32ToU16
|
||||
colorToU16 = u32ToU16
|
||||
|
||||
def u32ToColor(c):
|
||||
return c
|
||||
def u32ToColor(c):
|
||||
return c
|
||||
|
||||
u16ToColor = u16ToU32
|
||||
u16ToColor = u16ToU32
|
||||
|
|
|
@ -8,29 +8,33 @@ from . import createCallback
|
|||
|
||||
createCallback("mLoggerPy", "log", "_pyLog")
|
||||
|
||||
defaultLogger = None
|
||||
|
||||
def installDefault(logger):
|
||||
lib.mLogSetDefaultLogger(logger._native)
|
||||
global defaultLogger
|
||||
defaultLogger = logger
|
||||
lib.mLogSetDefaultLogger(logger._native)
|
||||
|
||||
class Logger(object):
|
||||
FATAL = lib.mLOG_FATAL
|
||||
DEBUG = lib.mLOG_DEBUG
|
||||
INFO = lib.mLOG_INFO
|
||||
WARN = lib.mLOG_WARN
|
||||
ERROR = lib.mLOG_ERROR
|
||||
STUB = lib.mLOG_STUB
|
||||
GAME_ERROR = lib.mLOG_GAME_ERROR
|
||||
FATAL = lib.mLOG_FATAL
|
||||
DEBUG = lib.mLOG_DEBUG
|
||||
INFO = lib.mLOG_INFO
|
||||
WARN = lib.mLOG_WARN
|
||||
ERROR = lib.mLOG_ERROR
|
||||
STUB = lib.mLOG_STUB
|
||||
GAME_ERROR = lib.mLOG_GAME_ERROR
|
||||
|
||||
def __init__(self):
|
||||
self._handle = ffi.new_handle(self)
|
||||
self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
|
||||
def __init__(self):
|
||||
self._handle = ffi.new_handle(self)
|
||||
self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
|
||||
|
||||
@staticmethod
|
||||
def categoryName(category):
|
||||
return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8')
|
||||
@staticmethod
|
||||
def categoryName(category):
|
||||
return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8')
|
||||
|
||||
def log(self, category, level, message):
|
||||
print("{}: {}".format(self.categoryName(category), message))
|
||||
def log(self, category, level, message):
|
||||
print("{}: {}".format(self.categoryName(category), message))
|
||||
|
||||
class NullLogger(Logger):
|
||||
def log(self, category, level, message):
|
||||
pass
|
||||
def log(self, category, level, message):
|
||||
pass
|
||||
|
|
|
@ -23,6 +23,7 @@ setup(name="${BINARY_NAME}",
|
|||
packages=["mgba"],
|
||||
setup_requires=['cffi>=1.6'],
|
||||
install_requires=['cffi>=1.6', 'cached-property'],
|
||||
extras_require={'pil': ['Pillow>=2.3']},
|
||||
cffi_modules=["_builder.py:ffi"],
|
||||
license="MPL 2.0",
|
||||
classifiers=classifiers
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>© 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
<string>© 2013 – 2017 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
Game Boy and Game Boy Advance are registered trademarks of Nintendo Co., Ltd.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
|
|
|
@ -26,6 +26,7 @@ endif()
|
|||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/input)
|
||||
|
||||
find_package(Qt5Multimedia)
|
||||
find_package(Qt5OpenGL)
|
||||
|
@ -76,10 +77,6 @@ set(SOURCE_FILES
|
|||
GamepadButtonEvent.cpp
|
||||
GamepadHatEvent.cpp
|
||||
IOViewer.cpp
|
||||
InputController.cpp
|
||||
InputItem.cpp
|
||||
InputModel.cpp
|
||||
InputProfile.cpp
|
||||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogController.cpp
|
||||
|
@ -104,7 +101,12 @@ set(SOURCE_FILES
|
|||
utils.cpp
|
||||
Window.cpp
|
||||
VFileDevice.cpp
|
||||
VideoView.cpp)
|
||||
VideoView.cpp
|
||||
input/InputController.cpp
|
||||
input/InputIndex.cpp
|
||||
input/InputItem.cpp
|
||||
input/InputModel.cpp
|
||||
input/InputProfile.cpp)
|
||||
|
||||
set(UI_FILES
|
||||
AboutScreen.ui
|
||||
|
@ -215,7 +217,7 @@ endif()
|
|||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
list(APPEND QT_DEFINES DATADIR="${DATADIR}")
|
||||
list(APPEND QT_DEFINES DATADIR="${CMAKE_INSTALL_PREFIX}/${DATADIR}")
|
||||
endif()
|
||||
|
||||
find_package(Qt5LinguistTools)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "GBAApp.h"
|
||||
|
||||
#include "AudioProcessor.h"
|
||||
#include "ConfigController.h"
|
||||
#include "Display.h"
|
||||
#include "GameController.h"
|
||||
#include "Window.h"
|
||||
|
@ -14,10 +15,8 @@
|
|||
#include <QFileInfo>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QIcon>
|
||||
#include <QTranslator>
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/internal/gba/video.h>
|
||||
#include <mgba-util/socket.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
|
@ -31,8 +30,9 @@ static GBAApp* g_app = nullptr;
|
|||
|
||||
mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
|
||||
|
||||
GBAApp::GBAApp(int& argc, char* argv[])
|
||||
GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
|
||||
: QApplication(argc, argv)
|
||||
, m_configController(config)
|
||||
{
|
||||
g_app = this;
|
||||
|
||||
|
@ -41,15 +41,9 @@ GBAApp::GBAApp(int& argc, char* argv[])
|
|||
#endif
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
setWindowIcon(QIcon(":/res/mgba-1024.png"));
|
||||
setWindowIcon(QIcon(":/res/mgba-512.png"));
|
||||
#endif
|
||||
|
||||
QTranslator* translator = new QTranslator(this);
|
||||
if (translator->load(QLocale(), QLatin1String(binaryName), QLatin1String("-"), QLatin1String(":/translations"))) {
|
||||
installTranslator(translator);
|
||||
}
|
||||
|
||||
|
||||
SocketSubsystemInit();
|
||||
qRegisterMetaType<const uint32_t*>("const uint32_t*");
|
||||
qRegisterMetaType<mCoreThread*>("mCoreThread*");
|
||||
|
@ -57,50 +51,15 @@ GBAApp::GBAApp(int& argc, char* argv[])
|
|||
QApplication::setApplicationName(projectName);
|
||||
QApplication::setApplicationVersion(projectVersion);
|
||||
|
||||
if (!m_configController.getQtOption("displayDriver").isNull()) {
|
||||
Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("displayDriver").toInt()));
|
||||
}
|
||||
|
||||
mArguments args;
|
||||
mGraphicsOpts graphicsOpts;
|
||||
mSubParser subparser;
|
||||
initParserForGraphics(&subparser, &graphicsOpts);
|
||||
bool loaded = m_configController.parseArguments(&args, argc, argv, &subparser);
|
||||
if (loaded && args.showHelp) {
|
||||
usage(argv[0], subparser.usage);
|
||||
::exit(0);
|
||||
return;
|
||||
if (!m_configController->getQtOption("displayDriver").isNull()) {
|
||||
Display::setDriver(static_cast<Display::Driver>(m_configController->getQtOption("displayDriver").toInt()));
|
||||
}
|
||||
|
||||
reloadGameDB();
|
||||
|
||||
if (!m_configController.getQtOption("audioDriver").isNull()) {
|
||||
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
|
||||
if (!m_configController->getQtOption("audioDriver").isNull()) {
|
||||
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController->getQtOption("audioDriver").toInt()));
|
||||
}
|
||||
Window* w = new Window(&m_configController);
|
||||
connect(w, &Window::destroyed, [this, w]() {
|
||||
m_windows.removeAll(w);
|
||||
});
|
||||
m_windows.append(w);
|
||||
|
||||
if (loaded) {
|
||||
w->argumentsPassed(&args);
|
||||
} else {
|
||||
w->loadConfig();
|
||||
}
|
||||
freeArguments(&args);
|
||||
|
||||
if (graphicsOpts.multiplier) {
|
||||
w->resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * graphicsOpts.multiplier, VIDEO_VERTICAL_PIXELS * graphicsOpts.multiplier));
|
||||
}
|
||||
if (graphicsOpts.fullscreen) {
|
||||
w->enterFullScreen();
|
||||
}
|
||||
|
||||
w->show();
|
||||
|
||||
w->controller()->setMultiplayerController(&m_multiplayer);
|
||||
w->multiplayerChanged();
|
||||
}
|
||||
|
||||
GBAApp::~GBAApp() {
|
||||
|
@ -122,7 +81,7 @@ Window* GBAApp::newWindow() {
|
|||
if (m_windows.count() >= MAX_GBAS) {
|
||||
return nullptr;
|
||||
}
|
||||
Window* w = new Window(&m_configController, m_multiplayer.attached());
|
||||
Window* w = new Window(m_configController, m_multiplayer.attached());
|
||||
int windowId = m_multiplayer.attached();
|
||||
connect(w, &Window::destroyed, [this, w]() {
|
||||
m_windows.removeAll(w);
|
||||
|
@ -165,10 +124,10 @@ void GBAApp::continueAll(const QList<Window*>& paused) {
|
|||
QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
|
||||
QList<Window*> paused;
|
||||
pauseAll(&paused);
|
||||
QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getOption("lastDirectory"), filter);
|
||||
QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
|
||||
continueAll(paused);
|
||||
if (!filename.isEmpty()) {
|
||||
m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
@ -176,10 +135,10 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr
|
|||
QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) {
|
||||
QList<Window*> paused;
|
||||
pauseAll(&paused);
|
||||
QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getOption("lastDirectory"), filter);
|
||||
QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
|
||||
continueAll(paused);
|
||||
if (!filename.isEmpty()) {
|
||||
m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
@ -187,10 +146,10 @@ QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QStr
|
|||
QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) {
|
||||
QList<Window*> paused;
|
||||
pauseAll(&paused);
|
||||
QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController.getOption("lastDirectory"));
|
||||
QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController->getOption("lastDirectory"));
|
||||
continueAll(paused);
|
||||
if (!filename.isEmpty()) {
|
||||
m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <QFileDialog>
|
||||
#include <QThread>
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "MultiplayerController.h"
|
||||
|
||||
struct NoIntroDB;
|
||||
|
@ -21,6 +20,7 @@ mLOG_DECLARE_CATEGORY(QT);
|
|||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
class GameController;
|
||||
class Window;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class GBAApp : public QApplication {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GBAApp(int& argc, char* argv[]);
|
||||
GBAApp(int& argc, char* argv[], ConfigController*);
|
||||
~GBAApp();
|
||||
static GBAApp* app();
|
||||
|
||||
|
@ -67,7 +67,7 @@ private:
|
|||
void pauseAll(QList<Window*>* paused);
|
||||
void continueAll(const QList<Window*>& paused);
|
||||
|
||||
ConfigController m_configController;
|
||||
ConfigController* m_configController;
|
||||
QList<Window*> m_windows;
|
||||
MultiplayerController m_multiplayer;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GBAKeyEditor.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPaintEvent>
|
||||
|
@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) {
|
|||
}
|
||||
|
||||
bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
|
||||
KeyEditor* keyEditor = static_cast<KeyEditor*>(obj);
|
||||
if (event->type() == QEvent::FocusOut) {
|
||||
keyEditor->setPalette(QApplication::palette(keyEditor));
|
||||
}
|
||||
if (event->type() != QEvent::FocusIn) {
|
||||
return false;
|
||||
}
|
||||
findFocus(static_cast<KeyEditor*>(obj));
|
||||
|
||||
QPalette palette = keyEditor->palette();
|
||||
palette.setBrush(keyEditor->backgroundRole(), palette.highlight());
|
||||
palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText());
|
||||
keyEditor->setPalette(palette);
|
||||
|
||||
findFocus(keyEditor);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "InputController.h"
|
||||
#include "LogController.h"
|
||||
#include "MultiplayerController.h"
|
||||
#include "Override.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
@ -162,6 +163,7 @@ GameController::GameController(QObject* parent)
|
|||
default:
|
||||
break;
|
||||
}
|
||||
mTileCacheDeinit(controller->m_tileCache.get());
|
||||
controller->m_tileCache.reset();
|
||||
}
|
||||
|
||||
|
@ -703,6 +705,7 @@ void GameController::threadContinue() {
|
|||
void GameController::frameAdvance() {
|
||||
if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
|
||||
setPaused(false);
|
||||
m_wasPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputItem.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
InputItem::InputItem(QAction* action, const QString& name, InputItem* parent)
|
||||
: m_action(action)
|
||||
, m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0])
|
||||
, m_keys(-1)
|
||||
, m_menu(nullptr)
|
||||
, m_name(name)
|
||||
, m_button(-1)
|
||||
, m_axis(-1)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_platform(PLATFORM_NONE)
|
||||
, m_parent(parent)
|
||||
{
|
||||
m_visibleName = action->text()
|
||||
.remove(QRegExp("&(?!&)"))
|
||||
.remove("...");
|
||||
}
|
||||
|
||||
InputItem::InputItem(InputItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, InputItem* parent)
|
||||
: m_action(nullptr)
|
||||
, m_shortcut(shortcut)
|
||||
, m_functions(functions)
|
||||
, m_keys(-1)
|
||||
, m_menu(nullptr)
|
||||
, m_name(name)
|
||||
, m_visibleName(visibleName)
|
||||
, m_button(-1)
|
||||
, m_axis(-1)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_platform(PLATFORM_NONE)
|
||||
, m_parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name, InputItem* parent)
|
||||
: m_action(nullptr)
|
||||
, m_shortcut(shortcut)
|
||||
, m_keys(key)
|
||||
, m_menu(nullptr)
|
||||
, m_name(name)
|
||||
, m_visibleName(visibleName)
|
||||
, m_button(-1)
|
||||
, m_axis(-1)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_platform(platform)
|
||||
, m_parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(QMenu* menu, InputItem* parent)
|
||||
: m_action(nullptr)
|
||||
, m_shortcut(0)
|
||||
, m_menu(menu)
|
||||
, m_button(-1)
|
||||
, m_axis(-1)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_parent(parent)
|
||||
{
|
||||
if (menu) {
|
||||
m_visibleName = menu->title()
|
||||
.remove(QRegExp("&(?!&)"))
|
||||
.remove("...");
|
||||
}
|
||||
}
|
||||
|
||||
void InputItem::addAction(QAction* action, const QString& name) {
|
||||
m_items.append(InputItem(action, name, this));
|
||||
}
|
||||
|
||||
void InputItem::addFunctions(InputItem::Functions functions,
|
||||
int shortcut, const QString& visibleName,
|
||||
const QString& name) {
|
||||
m_items.append(InputItem(functions, shortcut, visibleName, name, this));
|
||||
}
|
||||
|
||||
void InputItem::addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) {
|
||||
m_items.append(InputItem(platform, key, shortcut, visibleName, name, this));
|
||||
}
|
||||
|
||||
void InputItem::addSubmenu(QMenu* menu) {
|
||||
m_items.append(InputItem(menu, this));
|
||||
}
|
||||
|
||||
void InputItem::setShortcut(int shortcut) {
|
||||
m_shortcut = shortcut;
|
||||
if (m_action) {
|
||||
m_action->setShortcut(QKeySequence(shortcut));
|
||||
}
|
||||
}
|
||||
|
||||
void InputItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
|
||||
m_axis = axis;
|
||||
m_direction = direction;
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_ITEM
|
||||
#define QGBA_INPUT_ITEM
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadHatEvent.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QAction>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class InputItem {
|
||||
public:
|
||||
typedef QPair<std::function<void ()>, std::function<void ()>> Functions;
|
||||
|
||||
InputItem(QAction* action, const QString& name, InputItem* parent = nullptr);
|
||||
InputItem(Functions functions, int shortcut, const QString& visibleName,
|
||||
const QString& name, InputItem* parent = nullptr);
|
||||
InputItem(mPlatform platform, int key, int shortcut, const QString& name, const QString& visibleName, InputItem* parent = nullptr);
|
||||
InputItem(QMenu* action, InputItem* parent = nullptr);
|
||||
|
||||
QAction* action() { return m_action; }
|
||||
const QAction* action() const { return m_action; }
|
||||
int shortcut() const { return m_shortcut; }
|
||||
mPlatform platform() const { return m_platform; }
|
||||
Functions functions() const { return m_functions; }
|
||||
int key() const { return m_keys; }
|
||||
QMenu* menu() { return m_menu; }
|
||||
const QMenu* menu() const { return m_menu; }
|
||||
const QString& visibleName() const { return m_visibleName; }
|
||||
const QString& name() const { return m_name; }
|
||||
QList<InputItem>& items() { return m_items; }
|
||||
const QList<InputItem>& items() const { return m_items; }
|
||||
InputItem* parent() { return m_parent; }
|
||||
const InputItem* parent() const { return m_parent; }
|
||||
void addAction(QAction* action, const QString& name);
|
||||
void addFunctions(Functions functions, int shortcut, const QString& visibleName,
|
||||
const QString& name);
|
||||
void addKey(mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name);
|
||||
void addSubmenu(QMenu* menu);
|
||||
int button() const { return m_button; }
|
||||
void setShortcut(int sequence);
|
||||
void setButton(int button) { m_button = button; }
|
||||
int axis() const { return m_axis; }
|
||||
GamepadAxisEvent::Direction direction() const { return m_direction; }
|
||||
void setAxis(int axis, GamepadAxisEvent::Direction direction);
|
||||
|
||||
bool operator==(const InputItem& other) const {
|
||||
return m_menu == other.m_menu && m_action == other.m_action;
|
||||
}
|
||||
|
||||
private:
|
||||
QAction* m_action;
|
||||
int m_shortcut;
|
||||
QMenu* m_menu;
|
||||
Functions m_functions;
|
||||
QString m_name;
|
||||
QString m_visibleName;
|
||||
int m_button;
|
||||
int m_axis;
|
||||
int m_keys;
|
||||
GamepadAxisEvent::Direction m_direction;
|
||||
mPlatform m_platform;
|
||||
QList<InputItem> m_items;
|
||||
InputItem* m_parent;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,577 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputModel.h"
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputProfile.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
InputModel::InputModel(QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_rootMenu(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void InputModel::setConfigController(ConfigController* controller) {
|
||||
m_config = controller;
|
||||
}
|
||||
|
||||
QVariant InputModel::data(const QModelIndex& index, int role) const {
|
||||
if (role != Qt::DisplayRole || !index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
int row = index.row();
|
||||
const InputItem* item = static_cast<const InputItem*>(index.internalPointer());
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return item->visibleName();
|
||||
case 1:
|
||||
return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText);
|
||||
case 2:
|
||||
if (item->button() >= 0) {
|
||||
return item->button();
|
||||
}
|
||||
if (item->axis() >= 0) {
|
||||
char d = '\0';
|
||||
if (item->direction() == GamepadAxisEvent::POSITIVE) {
|
||||
d = '+';
|
||||
}
|
||||
if (item->direction() == GamepadAxisEvent::NEGATIVE) {
|
||||
d = '-';
|
||||
}
|
||||
return QString("%1%2").arg(d).arg(item->axis());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QAbstractItemModel::headerData(section, orientation, role);
|
||||
}
|
||||
if (orientation == Qt::Horizontal) {
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("Action");
|
||||
case 1:
|
||||
return tr("Keyboard");
|
||||
case 2:
|
||||
return tr("Gamepad");
|
||||
}
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
const InputItem* pmenu = &m_rootMenu;
|
||||
if (parent.isValid()) {
|
||||
pmenu = static_cast<InputItem*>(parent.internalPointer());
|
||||
}
|
||||
return createIndex(row, column, const_cast<InputItem*>(&pmenu->items()[row]));
|
||||
}
|
||||
|
||||
QModelIndex InputModel::parent(const QModelIndex& index) const {
|
||||
if (!index.isValid() || !index.internalPointer()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
InputItem* item = static_cast<InputItem*>(index.internalPointer());
|
||||
return this->index(item->parent());
|
||||
}
|
||||
|
||||
QModelIndex InputModel::index(InputItem* item) const {
|
||||
if (!item || !item->parent()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(item->parent()->items().indexOf(*item), 0, item);
|
||||
}
|
||||
|
||||
int InputModel::columnCount(const QModelIndex& index) const {
|
||||
return 3;
|
||||
}
|
||||
|
||||
int InputModel::rowCount(const QModelIndex& index) const {
|
||||
if (!index.isValid()) {
|
||||
return m_rootMenu.items().count();
|
||||
}
|
||||
const InputItem* item = static_cast<const InputItem*>(index.internalPointer());
|
||||
return item->items().count();
|
||||
}
|
||||
|
||||
InputItem* InputModel::add(QMenu* menu, std::function<void (InputItem*)> callback) {
|
||||
InputItem* smenu = m_menuMap[menu];
|
||||
if (!smenu) {
|
||||
return nullptr;
|
||||
}
|
||||
QModelIndex parent = index(smenu);
|
||||
beginInsertRows(parent, smenu->items().count(), smenu->items().count());
|
||||
callback(smenu);
|
||||
endInsertRows();
|
||||
InputItem* item = &smenu->items().last();
|
||||
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
|
||||
createIndex(smenu->items().count() - 1, 2, item));
|
||||
return item;
|
||||
}
|
||||
|
||||
void InputModel::addAction(QMenu* menu, QAction* action, const QString& name) {
|
||||
InputItem* item = add(menu, [&](InputItem* smenu) {
|
||||
smenu->addAction(action, name);
|
||||
});
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (m_config) {
|
||||
loadShortcuts(item);
|
||||
}
|
||||
}
|
||||
|
||||
void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
|
||||
int shortcut, const QString& visibleName, const QString& name) {
|
||||
InputItem* item = add(menu, [&](InputItem* smenu) {
|
||||
smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name);
|
||||
});
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool loadedShortcut = false;
|
||||
if (m_config) {
|
||||
loadedShortcut = loadShortcuts(item);
|
||||
}
|
||||
if (!loadedShortcut && !m_heldKeys.contains(shortcut)) {
|
||||
m_heldKeys[shortcut] = item;
|
||||
}
|
||||
}
|
||||
|
||||
void InputModel::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
|
||||
const QKeySequence& shortcut, const QString& visibleName, const QString& name) {
|
||||
addFunctions(menu, press, release, shortcut[0], visibleName, name);
|
||||
}
|
||||
|
||||
void InputModel::addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name) {
|
||||
InputItem* item = add(menu, [&](InputItem* smenu) {
|
||||
smenu->addKey(platform, key, shortcut, visibleName, name);
|
||||
});
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
bool loadedShortcut = false;
|
||||
if (m_config) {
|
||||
loadedShortcut = loadShortcuts(item);
|
||||
}
|
||||
if (!loadedShortcut && !m_keys.contains(qMakePair(platform, shortcut))) {
|
||||
m_keys[qMakePair(platform, shortcut)] = item;
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex InputModel::addMenu(QMenu* menu, QMenu* parentMenu) {
|
||||
InputItem* smenu = m_menuMap[parentMenu];
|
||||
if (!smenu) {
|
||||
smenu = &m_rootMenu;
|
||||
}
|
||||
QModelIndex parent = index(smenu);
|
||||
beginInsertRows(parent, smenu->items().count(), smenu->items().count());
|
||||
smenu->addSubmenu(menu);
|
||||
endInsertRows();
|
||||
InputItem* item = &smenu->items().last();
|
||||
emit dataChanged(createIndex(smenu->items().count() - 1, 0, item),
|
||||
createIndex(smenu->items().count() - 1, 2, item));
|
||||
m_menuMap[menu] = item;
|
||||
return index(item);
|
||||
}
|
||||
|
||||
InputItem* InputModel::itemAt(const QModelIndex& index) {
|
||||
if (!index.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (index.internalPointer()) {
|
||||
return static_cast<InputItem*>(index.internalPointer());
|
||||
}
|
||||
if (!index.parent().isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
|
||||
return &pmenu->items()[index.row()];
|
||||
}
|
||||
|
||||
const InputItem* InputModel::itemAt(const QModelIndex& index) const {
|
||||
if (!index.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (index.internalPointer()) {
|
||||
return static_cast<InputItem*>(index.internalPointer());
|
||||
}
|
||||
if (!index.parent().isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
InputItem* pmenu = static_cast<InputItem*>(index.parent().internalPointer());
|
||||
return &pmenu->items()[index.row()];
|
||||
}
|
||||
|
||||
int InputModel::shortcutAt(const QModelIndex& index) const {
|
||||
const InputItem* item = itemAt(index);
|
||||
if (!item) {
|
||||
return 0;
|
||||
}
|
||||
return item->shortcut();
|
||||
}
|
||||
|
||||
int InputModel::keyAt(const QModelIndex& index) const {
|
||||
const InputItem* item = itemAt(index);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
return item->key();
|
||||
}
|
||||
|
||||
bool InputModel::isMenuAt(const QModelIndex& index) const {
|
||||
const InputItem* item = itemAt(index);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
return item->menu();
|
||||
}
|
||||
|
||||
void InputModel::updateKey(const QModelIndex& index, int keySequence) {
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
const QModelIndex& parent = index.parent();
|
||||
if (!parent.isValid()) {
|
||||
return;
|
||||
}
|
||||
InputItem* item = itemAt(index);
|
||||
updateKey(item, keySequence);
|
||||
if (m_config) {
|
||||
m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION);
|
||||
}
|
||||
}
|
||||
|
||||
void InputModel::updateKey(InputItem* item, int keySequence) {
|
||||
int oldShortcut = item->shortcut();
|
||||
if (item->functions().first) {
|
||||
if (oldShortcut > 0) {
|
||||
m_heldKeys.take(oldShortcut);
|
||||
}
|
||||
if (keySequence > 0) {
|
||||
m_heldKeys[keySequence] = item;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->key() >= 0) {
|
||||
if (oldShortcut > 0) {
|
||||
m_keys.take(qMakePair(item->platform(), oldShortcut));
|
||||
}
|
||||
if (keySequence > 0) {
|
||||
m_keys[qMakePair(item->platform(), keySequence)] = item;
|
||||
}
|
||||
}
|
||||
|
||||
item->setShortcut(keySequence);
|
||||
|
||||
emit dataChanged(createIndex(index(item).row(), 0, item),
|
||||
createIndex(index(item).row(), 2, item));
|
||||
|
||||
emit keyRebound(index(item), keySequence);
|
||||
}
|
||||
|
||||
void InputModel::updateButton(const QModelIndex& index, int button) {
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
const QModelIndex& parent = index.parent();
|
||||
if (!parent.isValid()) {
|
||||
return;
|
||||
}
|
||||
InputItem* item = itemAt(index);
|
||||
int oldButton = item->button();
|
||||
if (oldButton >= 0) {
|
||||
m_buttons.take(oldButton);
|
||||
}
|
||||
updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
|
||||
item->setButton(button);
|
||||
if (button >= 0) {
|
||||
m_buttons[button] = item;
|
||||
}
|
||||
if (m_config) {
|
||||
m_config->setQtOption(item->name(), button, BUTTON_SECTION);
|
||||
if (!m_profileName.isNull()) {
|
||||
m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName);
|
||||
}
|
||||
}
|
||||
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||
createIndex(index.row(), 2, index.internalPointer()));
|
||||
|
||||
emit buttonRebound(index, button);
|
||||
}
|
||||
|
||||
void InputModel::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
const QModelIndex& parent = index.parent();
|
||||
if (!parent.isValid()) {
|
||||
return;
|
||||
}
|
||||
InputItem* item = itemAt(index);
|
||||
int oldAxis = item->axis();
|
||||
GamepadAxisEvent::Direction oldDirection = item->direction();
|
||||
if (oldAxis >= 0) {
|
||||
m_axes.take(qMakePair(oldAxis, oldDirection));
|
||||
}
|
||||
if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
|
||||
updateButton(index, -1);
|
||||
m_axes[qMakePair(axis, direction)] = item;
|
||||
}
|
||||
item->setAxis(axis, direction);
|
||||
if (m_config) {
|
||||
char d = '\0';
|
||||
if (direction == GamepadAxisEvent::POSITIVE) {
|
||||
d = '+';
|
||||
}
|
||||
if (direction == GamepadAxisEvent::NEGATIVE) {
|
||||
d = '-';
|
||||
}
|
||||
m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
|
||||
if (!m_profileName.isNull()) {
|
||||
m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName);
|
||||
}
|
||||
}
|
||||
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()),
|
||||
createIndex(index.row(), 2, index.internalPointer()));
|
||||
|
||||
emit axisRebound(index, axis, direction);
|
||||
}
|
||||
|
||||
void InputModel::clearKey(const QModelIndex& index) {
|
||||
updateKey(index, 0);
|
||||
}
|
||||
|
||||
void InputModel::clearButton(const QModelIndex& index) {
|
||||
updateButton(index, -1);
|
||||
}
|
||||
|
||||
bool InputModel::triggerKey(int keySequence, bool down, mPlatform platform) {
|
||||
auto key = m_keys.find(qMakePair(platform, keySequence));
|
||||
if (key != m_keys.end()) {
|
||||
m_keyCallback(key.value()->parent()->menu(), key.value()->key(), down);
|
||||
return true;
|
||||
}
|
||||
auto heldKey = m_heldKeys.find(keySequence);
|
||||
if (heldKey != m_heldKeys.end()) {
|
||||
auto pair = heldKey.value()->functions();
|
||||
if (down) {
|
||||
if (pair.first) {
|
||||
pair.first();
|
||||
}
|
||||
} else {
|
||||
if (pair.second) {
|
||||
pair.second();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputModel::triggerButton(int button, bool down) {
|
||||
auto item = m_buttons.find(button);
|
||||
if (item == m_buttons.end()) {
|
||||
return false;
|
||||
}
|
||||
if (down) {
|
||||
QAction* action = item.value()->action();
|
||||
if (action && action->isEnabled()) {
|
||||
action->trigger();
|
||||
}
|
||||
auto pair = item.value()->functions();
|
||||
if (pair.first) {
|
||||
pair.first();
|
||||
}
|
||||
} else {
|
||||
auto pair = item.value()->functions();
|
||||
if (pair.second) {
|
||||
pair.second();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputModel::triggerAxis(int axis, GamepadAxisEvent::Direction direction, bool isNew) {
|
||||
auto item = m_axes.find(qMakePair(axis, direction));
|
||||
if (item == m_axes.end()) {
|
||||
return false;
|
||||
}
|
||||
if (isNew) {
|
||||
QAction* action = item.value()->action();
|
||||
if (action && action->isEnabled()) {
|
||||
action->trigger();
|
||||
}
|
||||
}
|
||||
auto pair = item.value()->functions();
|
||||
if (isNew) {
|
||||
if (pair.first) {
|
||||
pair.first();
|
||||
}
|
||||
} else {
|
||||
if (pair.second) {
|
||||
pair.second();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputModel::loadShortcuts(InputItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return false;
|
||||
}
|
||||
loadGamepadShortcuts(item);
|
||||
QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
|
||||
if (!shortcut.isNull()) {
|
||||
if (shortcut.toString().endsWith("+")) {
|
||||
updateKey(item, toModifierShortcut(shortcut.toString()));
|
||||
} else {
|
||||
updateKey(item, QKeySequence(shortcut.toString())[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputModel::loadGamepadShortcuts(InputItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return;
|
||||
}
|
||||
QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION);
|
||||
int oldButton = item->button();
|
||||
if (oldButton >= 0) {
|
||||
m_buttons.take(oldButton);
|
||||
item->setButton(-1);
|
||||
}
|
||||
if (button.isNull() && m_profile) {
|
||||
int buttonInt;
|
||||
if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) {
|
||||
button = buttonInt;
|
||||
}
|
||||
}
|
||||
if (!button.isNull()) {
|
||||
item->setButton(button.toInt());
|
||||
m_buttons[button.toInt()] = item;
|
||||
}
|
||||
|
||||
QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);
|
||||
int oldAxis = item->axis();
|
||||
GamepadAxisEvent::Direction oldDirection = item->direction();
|
||||
if (oldAxis >= 0) {
|
||||
m_axes.take(qMakePair(oldAxis, oldDirection));
|
||||
item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
|
||||
}
|
||||
if (axis.isNull() && m_profile) {
|
||||
int axisInt;
|
||||
GamepadAxisEvent::Direction direction;
|
||||
if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) {
|
||||
axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt);
|
||||
}
|
||||
}
|
||||
if (!axis.isNull()) {
|
||||
QString axisDesc = axis.toString();
|
||||
if (axisDesc.size() >= 2) {
|
||||
GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
|
||||
if (axisDesc[0] == '-') {
|
||||
direction = GamepadAxisEvent::NEGATIVE;
|
||||
}
|
||||
if (axisDesc[0] == '+') {
|
||||
direction = GamepadAxisEvent::POSITIVE;
|
||||
}
|
||||
bool ok;
|
||||
int axis = axisDesc.mid(1).toInt(&ok);
|
||||
if (ok) {
|
||||
item->setAxis(axis, direction);
|
||||
m_axes[qMakePair(axis, direction)] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputModel::loadProfile(mPlatform platform, const QString& profile) {
|
||||
m_profileName = profile;
|
||||
m_profile = InputProfile::findProfile(platform, profile);
|
||||
onSubitems(&m_rootMenu, [this](InputItem* item) {
|
||||
loadGamepadShortcuts(item);
|
||||
});
|
||||
}
|
||||
|
||||
void InputModel::onSubitems(InputItem* item, std::function<void(InputItem*)> func) {
|
||||
for (InputItem& subitem : item->items()) {
|
||||
func(&subitem);
|
||||
onSubitems(&subitem, func);
|
||||
}
|
||||
}
|
||||
|
||||
int InputModel::toModifierShortcut(const QString& shortcut) {
|
||||
// Qt doesn't seem to work with raw modifier shortcuts!
|
||||
QStringList modifiers = shortcut.split('+');
|
||||
int value = 0;
|
||||
for (const auto& mod : modifiers) {
|
||||
if (mod == QLatin1String("Shift")) {
|
||||
value |= Qt::ShiftModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Ctrl")) {
|
||||
value |= Qt::ControlModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Alt")) {
|
||||
value |= Qt::AltModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Meta")) {
|
||||
value |= Qt::MetaModifier;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool InputModel::isModifierKey(int key) {
|
||||
switch (key) {
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_Control:
|
||||
case Qt::Key_Alt:
|
||||
case Qt::Key_Meta:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int InputModel::toModifierKey(int key) {
|
||||
int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
|
||||
key ^= modifiers;
|
||||
switch (key) {
|
||||
case Qt::Key_Shift:
|
||||
modifiers |= Qt::ShiftModifier;
|
||||
break;
|
||||
case Qt::Key_Control:
|
||||
modifiers |= Qt::ControlModifier;
|
||||
break;
|
||||
case Qt::Key_Alt:
|
||||
modifiers |= Qt::AltModifier;
|
||||
break;
|
||||
case Qt::Key_Meta:
|
||||
modifiers |= Qt::MetaModifier;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_MODEL
|
||||
#define QGBA_INPUT_MODEL
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "InputItem.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QAction;
|
||||
class QKeyEvent;
|
||||
class QMenu;
|
||||
class QString;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
class InputProfile;
|
||||
|
||||
class InputModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
constexpr static const char* const KEY_SECTION = "shortcutKey";
|
||||
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
|
||||
constexpr static const char* const AXIS_SECTION = "shortcutAxis";
|
||||
constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton.";
|
||||
constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis.";
|
||||
|
||||
public:
|
||||
InputModel(QObject* parent = nullptr);
|
||||
|
||||
void setConfigController(ConfigController* controller);
|
||||
void setProfile(const QString& profile);
|
||||
void setKeyCallback(std::function<void (QMenu*, int, bool)> callback) { m_keyCallback = callback; }
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override;
|
||||
virtual QModelIndex parent(const QModelIndex& index) const override;
|
||||
|
||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
void addAction(QMenu* menu, QAction* action, const QString& name);
|
||||
void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
|
||||
int shortcut, const QString& visibleName, const QString& name);
|
||||
void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release,
|
||||
const QKeySequence& shortcut, const QString& visibleName, const QString& name);
|
||||
void addKey(QMenu* menu, mPlatform platform, int key, int shortcut, const QString& visibleName, const QString& name);
|
||||
QModelIndex addMenu(QMenu* menu, QMenu* parent = nullptr);
|
||||
|
||||
QAction* getAction(const QString& name);
|
||||
int shortcutAt(const QModelIndex& index) const;
|
||||
int keyAt(const QModelIndex& index) const;
|
||||
bool isMenuAt(const QModelIndex& index) const;
|
||||
|
||||
void updateKey(const QModelIndex& index, int keySequence);
|
||||
void updateButton(const QModelIndex& index, int button);
|
||||
void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction);
|
||||
void updateHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction);
|
||||
|
||||
void clearKey(const QModelIndex& index);
|
||||
void clearButton(const QModelIndex& index);
|
||||
|
||||
static int toModifierShortcut(const QString& shortcut);
|
||||
static bool isModifierKey(int key);
|
||||
static int toModifierKey(int key);
|
||||
|
||||
void loadProfile(mPlatform platform, const QString& profile);
|
||||
|
||||
bool triggerKey(int keySequence, bool down, mPlatform platform = PLATFORM_NONE);
|
||||
bool triggerButton(int button, bool down);
|
||||
bool triggerAxis(int axis, GamepadAxisEvent::Direction, bool isNew);
|
||||
bool triggerHat(int hat, GamepadHatEvent::Direction);
|
||||
|
||||
signals:
|
||||
void keyRebound(const QModelIndex&, int keySequence);
|
||||
void buttonRebound(const QModelIndex&, int button);
|
||||
void axisRebound(const QModelIndex& index, int axis, GamepadAxisEvent::Direction);
|
||||
void hatRebound(const QModelIndex& index, int hat, GamepadHatEvent::Direction);
|
||||
|
||||
private:
|
||||
InputItem* add(QMenu* menu, std::function<void (InputItem*)>);
|
||||
InputItem* itemAt(const QModelIndex& index);
|
||||
const InputItem* itemAt(const QModelIndex& index) const;
|
||||
bool loadShortcuts(InputItem*);
|
||||
void loadGamepadShortcuts(InputItem*);
|
||||
void onSubitems(InputItem*, std::function<void(InputItem*)> func);
|
||||
void updateKey(InputItem* item, int keySequence);
|
||||
|
||||
QModelIndex index(InputItem* item) const;
|
||||
|
||||
InputItem m_rootMenu;
|
||||
QMap<QMenu*, InputItem*> m_menuMap;
|
||||
QMap<int, InputItem*> m_buttons;
|
||||
QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes;
|
||||
QMap<int, InputItem*> m_heldKeys;
|
||||
QMap<QPair<mPlatform, int>, InputItem*> m_keys;
|
||||
ConfigController* m_config;
|
||||
std::function<void (QMenu*, int key, bool down)> m_keyCallback;
|
||||
QString m_profileName;
|
||||
const InputProfile* m_profile;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,269 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputProfile.h"
|
||||
|
||||
#include "InputController.h"
|
||||
|
||||
#include <QRegExp>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
const InputProfile InputProfile::s_defaultMaps[] = {
|
||||
{
|
||||
"XInput Controller #\\d+", // XInput (Windows)
|
||||
{
|
||||
/*keyA */ 11,
|
||||
/*keyB */ 10,
|
||||
/*keySelect */ 5,
|
||||
/*keyStart */ 4,
|
||||
/*keyRight */ 3,
|
||||
/*keyLeft */ 2,
|
||||
/*keyUp */ 0,
|
||||
/*keyDown */ 1,
|
||||
/*keyR */ 9,
|
||||
/*keyL */ 8
|
||||
},
|
||||
{
|
||||
/*loadState */ 12,
|
||||
/*saveState */ 13,
|
||||
/*holdFastForward */ -1,
|
||||
/*holdRewind */ -1,
|
||||
},
|
||||
{
|
||||
/*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
/*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 4},
|
||||
}
|
||||
},
|
||||
{
|
||||
"(Microsoft X-Box 360 pad|Xbox Gamepad \\(userspace driver\\))", // Linux
|
||||
{
|
||||
/*keyA */ 1,
|
||||
/*keyB */ 0,
|
||||
/*keySelect */ 6,
|
||||
/*keyStart */ 7,
|
||||
/*keyRight */ -1,
|
||||
/*keyLeft */ -1,
|
||||
/*keyUp */ -1,
|
||||
/*keyDown */ -1,
|
||||
/*keyR */ 5,
|
||||
/*keyL */ 4
|
||||
},
|
||||
{
|
||||
/*loadState */ 2,
|
||||
/*saveState */ 3,
|
||||
/*holdFastForward */ -1,
|
||||
/*holdRewind */ -1,
|
||||
},
|
||||
{
|
||||
/*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
/*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||
}
|
||||
},
|
||||
{
|
||||
"Xbox 360 Wired Controller", // OS X
|
||||
{
|
||||
/*keyA */ 1,
|
||||
/*keyB */ 0,
|
||||
/*keySelect */ 9,
|
||||
/*keyStart */ 8,
|
||||
/*keyRight */ 14,
|
||||
/*keyLeft */ 13,
|
||||
/*keyUp */ 11,
|
||||
/*keyDown */ 12,
|
||||
/*keyR */ 5,
|
||||
/*keyL */ 4
|
||||
},
|
||||
{
|
||||
/*loadState */ 2,
|
||||
/*saveState */ 3,
|
||||
/*holdFastForward */ -1,
|
||||
/*holdRewind */ -1,
|
||||
},
|
||||
{
|
||||
/*loadState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*saveState */ {GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
/*holdFastForward */ {GamepadAxisEvent::Direction::POSITIVE, 5},
|
||||
/*holdRewind */ {GamepadAxisEvent::Direction::POSITIVE, 2},
|
||||
}
|
||||
},
|
||||
{
|
||||
"(Sony Computer Entertainment )?Wireless Controller", // The DualShock 4 device ID is cut off on Windows
|
||||
{
|
||||
/*keyA */ 1,
|
||||
/*keyB */ 2,
|
||||
/*keySelect */ 8,
|
||||
/*keyStart */ 9,
|
||||
/*keyRight */ -1,
|
||||
/*keyLeft */ -1,
|
||||
/*keyUp */ -1,
|
||||
/*keyDown */ -1,
|
||||
/*keyR */ 5,
|
||||
/*keyL */ 4
|
||||
},
|
||||
{
|
||||
/*loadState */ 0,
|
||||
/*saveState */ 3,
|
||||
/*holdFastForward */ 7,
|
||||
/*holdRewind */ 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
"PLAYSTATION\\(R\\)3 Controller", // DualShock 3 (OS X)
|
||||
{
|
||||
/*keyA */ 13,
|
||||
/*keyB */ 14,
|
||||
/*keySelect */ 0,
|
||||
/*keyStart */ 3,
|
||||
/*keyRight */ 5,
|
||||
/*keyLeft */ 7,
|
||||
/*keyUp */ 4,
|
||||
/*keyDown */ 6,
|
||||
/*keyR */ 11,
|
||||
/*keyL */ 10
|
||||
},
|
||||
{
|
||||
/*loadState */ 15,
|
||||
/*saveState */ 12,
|
||||
/*holdFastForward */ 9,
|
||||
/*holdRewind */ 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
"Wiimote \\(..-..-..-..-..-..\\)", // WJoy (OS X)
|
||||
{
|
||||
/*keyA */ 15,
|
||||
/*keyB */ 16,
|
||||
/*keySelect */ 7,
|
||||
/*keyStart */ 6,
|
||||
/*keyRight */ 14,
|
||||
/*keyLeft */ 13,
|
||||
/*keyUp */ 11,
|
||||
/*keyDown */ 12,
|
||||
/*keyR */ 20,
|
||||
/*keyL */ 19
|
||||
},
|
||||
{
|
||||
/*loadState */ 18,
|
||||
/*saveState */ 17,
|
||||
/*holdFastForward */ 22,
|
||||
/*holdRewind */ 21,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constexpr InputProfile::InputProfile(const char* name,
|
||||
const KeyList<int> keys,
|
||||
const Shortcuts<int> shortcutButtons,
|
||||
const Shortcuts<Axis> shortcutAxes,
|
||||
const KeyList<AxisValue> axes,
|
||||
const struct Coord& tiltAxis,
|
||||
const struct Coord& gyroAxis,
|
||||
float gyroSensitivity)
|
||||
: m_profileName(name)
|
||||
, m_keys {
|
||||
keys.keyA,
|
||||
keys.keyB,
|
||||
keys.keySelect,
|
||||
keys.keyStart,
|
||||
keys.keyRight,
|
||||
keys.keyLeft,
|
||||
keys.keyUp,
|
||||
keys.keyDown,
|
||||
keys.keyR,
|
||||
keys.keyL,
|
||||
}
|
||||
, m_axes {
|
||||
axes.keyA,
|
||||
axes.keyB,
|
||||
axes.keySelect,
|
||||
axes.keyStart,
|
||||
axes.keyRight,
|
||||
axes.keyLeft,
|
||||
axes.keyUp,
|
||||
axes.keyDown,
|
||||
axes.keyR,
|
||||
axes.keyL,
|
||||
}
|
||||
, m_shortcutButtons(shortcutButtons)
|
||||
, m_shortcutAxes(shortcutAxes)
|
||||
, m_tiltAxis(tiltAxis)
|
||||
, m_gyroAxis(gyroAxis)
|
||||
, m_gyroSensitivity(gyroSensitivity)
|
||||
{
|
||||
}
|
||||
|
||||
const InputProfile* InputProfile::findProfile(mPlatform platform, const QString& name) {
|
||||
// TODO: Use platform
|
||||
for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) {
|
||||
QRegExp re(s_defaultMaps[i].m_profileName);
|
||||
if (re.exactMatch(name)) {
|
||||
return &s_defaultMaps[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InputProfile::apply(mPlatform platform, InputController* controller) const {
|
||||
for (size_t i = 0; i < GBA_KEY_MAX; ++i) {
|
||||
#ifdef BUILD_SDL
|
||||
controller->bindKey(platform, SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i));
|
||||
controller->bindAxis(platform, SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i));
|
||||
#endif
|
||||
}
|
||||
controller->registerTiltAxisX(m_tiltAxis.x);
|
||||
controller->registerTiltAxisY(m_tiltAxis.y);
|
||||
controller->registerGyroAxisX(m_gyroAxis.x);
|
||||
controller->registerGyroAxisY(m_gyroAxis.y);
|
||||
controller->setGyroSensitivity(m_gyroSensitivity);
|
||||
}
|
||||
|
||||
bool InputProfile::lookupShortcutButton(const QString& shortcutName, int* button) const {
|
||||
if (shortcutName == QLatin1String("loadState")) {
|
||||
*button = m_shortcutButtons.loadState;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("saveState")) {
|
||||
*button = m_shortcutButtons.saveState;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("holdFastForward")) {
|
||||
*button = m_shortcutButtons.holdFastForward;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("holdRewind")) {
|
||||
*button = m_shortcutButtons.holdRewind;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputProfile::lookupShortcutAxis(const QString& shortcutName, int* axis, GamepadAxisEvent::Direction* direction) const {
|
||||
if (shortcutName == QLatin1String("loadState")) {
|
||||
*axis = m_shortcutAxes.loadState.axis;
|
||||
*direction = m_shortcutAxes.loadState.direction;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("saveState")) {
|
||||
*axis = m_shortcutAxes.saveState.axis;
|
||||
*direction = m_shortcutAxes.saveState.direction;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("holdFastForward")) {
|
||||
*axis = m_shortcutAxes.holdFastForward.axis;
|
||||
*direction = m_shortcutAxes.holdFastForward.direction;
|
||||
return true;
|
||||
}
|
||||
if (shortcutName == QLatin1String("holdRewind")) {
|
||||
*axis = m_shortcutAxes.holdRewind.axis;
|
||||
*direction = m_shortcutAxes.holdRewind.direction;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_PROFILE
|
||||
#define QGBA_INPUT_PROFILE
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/gba/interface.h>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class InputController;
|
||||
|
||||
class InputProfile {
|
||||
public:
|
||||
static const InputProfile* findProfile(mPlatform platform, const QString& name);
|
||||
|
||||
void apply(mPlatform platform, InputController*) const;
|
||||
bool lookupShortcutButton(const QString& shortcut, int* button) const;
|
||||
bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const;
|
||||
|
||||
private:
|
||||
struct Coord {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
struct AxisValue {
|
||||
GamepadAxisEvent::Direction direction;
|
||||
int axis;
|
||||
};
|
||||
|
||||
template <typename T> struct Shortcuts {
|
||||
T loadState;
|
||||
T saveState;
|
||||
T holdFastForward;
|
||||
T holdRewind;
|
||||
};
|
||||
|
||||
struct Axis {
|
||||
GamepadAxisEvent::Direction direction;
|
||||
int axis;
|
||||
};
|
||||
|
||||
template <typename T> struct KeyList {
|
||||
T keyA;
|
||||
T keyB;
|
||||
T keySelect;
|
||||
T keyStart;
|
||||
T keyRight;
|
||||
T keyLeft;
|
||||
T keyUp;
|
||||
T keyDown;
|
||||
T keyR;
|
||||
T keyL;
|
||||
};
|
||||
|
||||
constexpr InputProfile(const char* name,
|
||||
const KeyList<int> keys,
|
||||
const Shortcuts<int> shortcutButtons = { -1, -1, -1, -1},
|
||||
const Shortcuts<Axis> shortcutAxes = {
|
||||
{GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
{GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
{GamepadAxisEvent::Direction::NEUTRAL, -1},
|
||||
{GamepadAxisEvent::Direction::NEUTRAL, -1}},
|
||||
const KeyList<AxisValue> axes = {
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 },
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 },
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 },
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 },
|
||||
{ GamepadAxisEvent::Direction::POSITIVE, 0 },
|
||||
{ GamepadAxisEvent::Direction::NEGATIVE, 0 },
|
||||
{ GamepadAxisEvent::Direction::NEGATIVE, 1 },
|
||||
{ GamepadAxisEvent::Direction::POSITIVE, 1 },
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 },
|
||||
{ GamepadAxisEvent::Direction::NEUTRAL, -1 }},
|
||||
const struct Coord& tiltAxis = { 2, 3 },
|
||||
const struct Coord& gyroAxis = { 0, 1 },
|
||||
float gyroSensitivity = 2e+09f);
|
||||
|
||||
static const InputProfile s_defaultMaps[];
|
||||
|
||||
const char* m_profileName;
|
||||
const int m_keys[GBA_KEY_MAX];
|
||||
const AxisValue m_axes[GBA_KEY_MAX];
|
||||
const Shortcuts<int> m_shortcutButtons;
|
||||
const Shortcuts<Axis> m_shortcutAxes;
|
||||
Coord m_tiltAxis;
|
||||
Coord m_gyroAxis;
|
||||
float m_gyroSensitivity;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputModel.h"
|
||||
#include "InputIndex.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QKeyEvent>
|
||||
|
@ -99,7 +99,7 @@ void KeyEditor::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
m_lastKey.start(KEY_TIME);
|
||||
if (m_key) {
|
||||
if (InputModel::isModifierKey(m_key)) {
|
||||
if (InputIndex::isModifierKey(m_key)) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Shift:
|
||||
setValue(Qt::ShiftModifier);
|
||||
|
@ -115,7 +115,7 @@ void KeyEditor::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (InputModel::isModifierKey(event->key())) {
|
||||
if (InputIndex::isModifierKey(event->key())) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Shift:
|
||||
setValue(m_key | Qt::ShiftModifier);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <mgba/core/core.h>
|
||||
|
||||
#include "GameController.h"
|
||||
#include "MemoryView.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -24,6 +25,7 @@ MemorySearch::MemorySearch(GameController* controller, QWidget* parent)
|
|||
connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||
connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||
connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||
connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory);
|
||||
}
|
||||
|
||||
MemorySearch::~MemorySearch() {
|
||||
|
@ -47,7 +49,6 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) {
|
|||
params->type = mCORE_MEMORY_SEARCH_32;
|
||||
}
|
||||
if (m_ui.numHex->isChecked()) {
|
||||
bool ok;
|
||||
uint32_t v = m_ui.value->text().toUInt(&ok, 16);
|
||||
if (ok) {
|
||||
switch (params->type) {
|
||||
|
@ -150,6 +151,7 @@ void MemorySearch::refresh() {
|
|||
mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i);
|
||||
QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0')));
|
||||
m_ui.results->setItem(i, 0, item);
|
||||
QTableWidgetItem* type;
|
||||
if (m_ui.numHex->isChecked()) {
|
||||
switch (result->type) {
|
||||
case mCORE_MEMORY_SEARCH_8:
|
||||
|
@ -181,7 +183,44 @@ void MemorySearch::refresh() {
|
|||
item = new QTableWidgetItem("?"); // TODO
|
||||
}
|
||||
}
|
||||
QString divisor;
|
||||
if (result->guessDivisor > 1) {
|
||||
divisor = tr(" (⅟%0×)").arg(result->guessDivisor);
|
||||
}
|
||||
switch (result->type) {
|
||||
case mCORE_MEMORY_SEARCH_8:
|
||||
type = new QTableWidgetItem(tr("1 byte%0").arg(divisor));
|
||||
break;
|
||||
case mCORE_MEMORY_SEARCH_16:
|
||||
type = new QTableWidgetItem(tr("2 bytes%0").arg(divisor));
|
||||
break;
|
||||
case mCORE_MEMORY_SEARCH_GUESS:
|
||||
case mCORE_MEMORY_SEARCH_32:
|
||||
type = new QTableWidgetItem(tr("4 bytes%0").arg(divisor));
|
||||
break;
|
||||
case mCORE_MEMORY_SEARCH_STRING:
|
||||
item = new QTableWidgetItem("?"); // TODO
|
||||
}
|
||||
m_ui.results->setItem(i, 1, item);
|
||||
m_ui.results->setItem(i, 2, type);
|
||||
}
|
||||
m_ui.results->sortItems(0);
|
||||
m_ui.results->resizeColumnsToContents();
|
||||
m_ui.results->resizeRowsToContents();
|
||||
}
|
||||
|
||||
void MemorySearch::openMemory() {
|
||||
auto items = m_ui.results->selectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
QTableWidgetItem* item = items[0];
|
||||
uint32_t address = item->text().toUInt(nullptr, 16);
|
||||
|
||||
MemoryView* memView = new MemoryView(m_controller);
|
||||
memView->jumpToAddress(address);
|
||||
|
||||
connect(m_controller, &GameController::gameStopped, memView, &QWidget::close);
|
||||
memView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
memView->show();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ public slots:
|
|||
void search();
|
||||
void searchWithin();
|
||||
|
||||
private slots:
|
||||
void openMemory();
|
||||
|
||||
private:
|
||||
bool createParams(mCoreMemorySearchParams*);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string>Memory Search</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
|
@ -28,6 +28,9 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
|
@ -194,11 +197,8 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewMem">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View in Memory View</string>
|
||||
<string>Open in Memory Viewer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -214,10 +214,26 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>MemorySearch</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>315</x>
|
||||
<y>357</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>315</x>
|
||||
<y>188</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="type"/>
|
||||
<buttongroup name="width"/>
|
||||
<buttongroup name="numType"/>
|
||||
<buttongroup name="type"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void update();
|
||||
void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); }
|
||||
|
||||
private slots:
|
||||
void setIndex(int);
|
||||
|
|
|
@ -68,6 +68,7 @@ void ObjView::translateIndex(int index) {
|
|||
|
||||
#ifdef M_CORE_GBA
|
||||
void ObjView::updateTilesGBA(bool force) {
|
||||
m_ui.objId->setMaximum(127);
|
||||
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
|
||||
const GBAObj* obj = &gba->video.oam.obj[m_objId];
|
||||
|
||||
|
@ -172,6 +173,7 @@ void ObjView::updateTilesGBA(bool force) {
|
|||
|
||||
#ifdef M_CORE_GB
|
||||
void ObjView::updateTilesGB(bool force) {
|
||||
m_ui.objId->setMaximum(39);
|
||||
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "OverrideView.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "ConfigController.h"
|
||||
|
@ -79,6 +80,21 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
|
|||
connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides);
|
||||
connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides);
|
||||
|
||||
QPalette palette = m_ui.color0->palette();
|
||||
palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8));
|
||||
m_ui.color0->setPalette(palette);
|
||||
palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8));
|
||||
m_ui.color1->setPalette(palette);
|
||||
palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50));
|
||||
m_ui.color2->setPalette(palette);
|
||||
palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00));
|
||||
m_ui.color3->setPalette(palette);
|
||||
|
||||
m_ui.color0->installEventFilter(this);
|
||||
m_ui.color1->installEventFilter(this);
|
||||
m_ui.color2->installEventFilter(this);
|
||||
m_ui.color3->installEventFilter(this);
|
||||
|
||||
connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides);
|
||||
#ifndef M_CORE_GBA
|
||||
m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA));
|
||||
|
@ -96,6 +112,42 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
|
|||
}
|
||||
}
|
||||
|
||||
bool OverrideView::eventFilter(QObject* obj, QEvent* event) {
|
||||
#ifdef M_CORE_GB
|
||||
if (event->type() != QEvent::MouseButtonRelease) {
|
||||
return false;
|
||||
}
|
||||
int colorId;
|
||||
if (obj == m_ui.color0) {
|
||||
colorId = 0;
|
||||
} else if (obj == m_ui.color1) {
|
||||
colorId = 1;
|
||||
} else if (obj == m_ui.color2) {
|
||||
colorId = 2;
|
||||
} else if (obj == m_ui.color3) {
|
||||
colorId = 3;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
QWidget* swatch = static_cast<QWidget*>(obj);
|
||||
|
||||
QColorDialog* colorPicker = new QColorDialog;
|
||||
colorPicker->setAttribute(Qt::WA_DeleteOnClose);
|
||||
colorPicker->open();
|
||||
connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) {
|
||||
QPalette palette = swatch->palette();
|
||||
palette.setColor(backgroundRole(), color);
|
||||
swatch->setPalette(palette);
|
||||
m_gbColors[colorId] = color.rgb();
|
||||
updateOverrides();
|
||||
});
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OverrideView::saveOverride() {
|
||||
if (!m_config) {
|
||||
return;
|
||||
|
@ -155,7 +207,13 @@ void OverrideView::updateOverrides() {
|
|||
GBOverride* gb = new GBOverride;
|
||||
gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()];
|
||||
gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()];
|
||||
if (gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT) {
|
||||
gb->override.gbColors[0] = m_gbColors[0];
|
||||
gb->override.gbColors[1] = m_gbColors[1];
|
||||
gb->override.gbColors[2] = m_gbColors[2];
|
||||
gb->override.gbColors[3] = m_gbColors[3];
|
||||
bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT;
|
||||
hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]);
|
||||
if (hasOverride) {
|
||||
m_controller->setOverride(gb);
|
||||
} else {
|
||||
m_controller->clearOverride();
|
||||
|
|
|
@ -36,6 +36,9 @@ private slots:
|
|||
void gameStarted(mCoreThread*);
|
||||
void gameStopped();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
private:
|
||||
Ui::OverrideView m_ui;
|
||||
|
||||
|
@ -43,6 +46,8 @@ private:
|
|||
ConfigController* m_config;
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
uint32_t m_gbColors[4]{};
|
||||
|
||||
static QList<enum GBModel> s_gbModelList;
|
||||
static QList<enum GBMemoryBankControllerType> s_mbcList;
|
||||
#endif
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>443</width>
|
||||
<height>282</height>
|
||||
<width>444</width>
|
||||
<height>284</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -326,6 +326,93 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="color0">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color1">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
@ -13,13 +13,15 @@
|
|||
#include "ShortcutView.h"
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
SettingsView::SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent)
|
||||
SettingsView::SettingsView(ConfigController* controller, InputController* inputController, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
, m_controller(controller)
|
||||
, m_input(inputController)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
|
@ -88,6 +90,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
m_ui.patchPath->setText(path);
|
||||
}
|
||||
});
|
||||
connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared);
|
||||
|
||||
// TODO: Move to reloadConfig()
|
||||
QVariant audioDriver = m_controller->getQtOption("audioDriver");
|
||||
|
@ -152,11 +155,29 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
}
|
||||
});
|
||||
|
||||
ShortcutView* shortcutView = new ShortcutView();
|
||||
shortcutView->setModel(inputModel);
|
||||
shortcutView->setInputController(inputController);
|
||||
m_ui.stackedWidget->addWidget(shortcutView);
|
||||
m_ui.tabs->addItem(tr("Bindings"));
|
||||
m_ui.languages->setItemData(0, QLocale("en"));
|
||||
QDir ts(":/translations/");
|
||||
for (auto name : ts.entryList()) {
|
||||
if (!name.endsWith(".qm")) {
|
||||
continue;
|
||||
}
|
||||
QLocale locale(name.remove(QString("%0-").arg(binaryName)).remove(".qm"));
|
||||
m_ui.languages->addItem(locale.nativeLanguageName(), locale);
|
||||
if (locale == QLocale()) {
|
||||
m_ui.languages->setCurrentIndex(m_ui.languages->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
m_keyView = new ShortcutView();
|
||||
m_keyView->setModel(inputController->keyIndex());
|
||||
m_keyView->setInputController(inputController);
|
||||
m_shortcutView = new ShortcutView();
|
||||
m_shortcutView->setModel(inputController->inputIndex());
|
||||
m_shortcutView->setInputController(inputController);
|
||||
m_ui.stackedWidget->addWidget(m_keyView);
|
||||
m_ui.tabs->addItem(tr("Controls"));
|
||||
m_ui.stackedWidget->addWidget(m_shortcutView);
|
||||
m_ui.tabs->addItem(tr("Shortcuts"));
|
||||
}
|
||||
|
||||
void SettingsView::selectBios(QLineEdit* bios) {
|
||||
|
@ -244,8 +265,18 @@ void SettingsView::updateConfig() {
|
|||
emit displayDriverChanged();
|
||||
}
|
||||
|
||||
QLocale language = m_ui.languages->itemData(m_ui.languages->currentIndex()).toLocale();
|
||||
if (language != m_controller->getQtOption("language").toLocale() && !(language.bcp47Name() == QLocale::system().bcp47Name() && m_controller->getQtOption("language").isNull())) {
|
||||
m_controller->setQtOption("language", language.bcp47Name());
|
||||
emit languageChanged();
|
||||
}
|
||||
|
||||
m_controller->write();
|
||||
|
||||
m_input->rebuildIndex(m_shortcutView->root());
|
||||
m_input->rebuildKeyIndex(m_keyView->root());
|
||||
m_input->saveConfiguration();
|
||||
|
||||
emit pathsChanged();
|
||||
emit biosLoaded(PLATFORM_GBA, m_ui.gbaBios->text());
|
||||
}
|
||||
|
|
|
@ -16,19 +16,22 @@ namespace QGBA {
|
|||
|
||||
class ConfigController;
|
||||
class InputController;
|
||||
class InputModel;
|
||||
class InputIndex;
|
||||
class ShortcutView;
|
||||
|
||||
class SettingsView : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SettingsView(ConfigController* controller, InputController* inputController, InputModel* inputModel, QWidget* parent = nullptr);
|
||||
SettingsView(ConfigController* controller, InputController* inputController, QWidget* parent = nullptr);
|
||||
|
||||
signals:
|
||||
void biosLoaded(int platform, const QString&);
|
||||
void audioDriverChanged();
|
||||
void displayDriverChanged();
|
||||
void pathsChanged();
|
||||
void languageChanged();
|
||||
void libraryCleared();
|
||||
|
||||
private slots:
|
||||
void selectBios(QLineEdit*);
|
||||
|
@ -40,6 +43,8 @@ private:
|
|||
|
||||
ConfigController* m_controller;
|
||||
InputController* m_input;
|
||||
ShortcutView* m_shortcutView;
|
||||
ShortcutView* m_keyView;
|
||||
|
||||
void saveSetting(const char* key, const QAbstractButton*);
|
||||
void saveSetting(const char* key, const QComboBox*);
|
||||
|
|
|
@ -398,17 +398,30 @@
|
|||
</widget>
|
||||
<widget class="QWidget" name="interface_2">
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="showLibrary">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Show when no game open</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="languages">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>English</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Library:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="libraryStyle">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
@ -422,38 +435,38 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="showLibrary">
|
||||
<property name="text">
|
||||
<string>Library:</string>
|
||||
<string>Show when no game open</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="clearCache">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="Line" name="line_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="allowOpposingDirections">
|
||||
<property name="text">
|
||||
<string>Allow opposing input directions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="suspendScreensaver">
|
||||
<property name="text">
|
||||
<string>Suspend screensaver</string>
|
||||
|
@ -463,13 +476,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="pauseOnFocusLost">
|
||||
<property name="text">
|
||||
<string>Pause when inactive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="Line" name="line_10">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="emulation">
|
||||
|
|
|
@ -15,9 +15,11 @@ using namespace QGBA;
|
|||
|
||||
ShortcutView::ShortcutView(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_model()
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.keyEdit->setValueKey(0);
|
||||
m_ui.shortcutTable->setModel(&m_model);
|
||||
|
||||
connect(m_ui.gamepadButton, &QAbstractButton::pressed, [this]() {
|
||||
bool signalsBlocked = m_ui.keyEdit->blockSignals(true);
|
||||
|
@ -33,15 +35,19 @@ ShortcutView::ShortcutView(QWidget* parent)
|
|||
connect(m_ui.keyEdit, &KeyEditor::axisChanged, this, &ShortcutView::updateAxis);
|
||||
connect(m_ui.shortcutTable, &QAbstractItemView::doubleClicked, this, &ShortcutView::load);
|
||||
connect(m_ui.clearButton, &QAbstractButton::clicked, this, &ShortcutView::clear);
|
||||
#ifdef BUILD_SDL
|
||||
connect(m_ui.gamepadName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_input->setGamepad(SDL_BINDING_BUTTON, index);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
ShortcutView::~ShortcutView() {
|
||||
m_input->releaseFocus(this);
|
||||
}
|
||||
|
||||
void ShortcutView::setModel(InputModel* model) {
|
||||
m_controller = model;
|
||||
m_ui.shortcutTable->setModel(model);
|
||||
void ShortcutView::setModel(InputIndex* model) {
|
||||
m_model.clone(*model);
|
||||
}
|
||||
|
||||
void ShortcutView::setInputController(InputController* controller) {
|
||||
|
@ -50,16 +56,33 @@ void ShortcutView::setInputController(InputController* controller) {
|
|||
}
|
||||
m_input = controller;
|
||||
m_input->stealFocus(this);
|
||||
updateGamepads();
|
||||
}
|
||||
|
||||
void ShortcutView::updateGamepads() {
|
||||
if (!m_input) {
|
||||
return;
|
||||
}
|
||||
#ifdef BUILD_SDL
|
||||
m_ui.gamepadName->clear();
|
||||
|
||||
QStringList gamepads = m_input->connectedGamepads(SDL_BINDING_BUTTON);
|
||||
int activeGamepad = m_input->gamepad(SDL_BINDING_BUTTON);
|
||||
|
||||
for (const auto& gamepad : gamepads) {
|
||||
m_ui.gamepadName->addItem(gamepad);
|
||||
}
|
||||
m_ui.gamepadName->setCurrentIndex(activeGamepad);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void ShortcutView::load(const QModelIndex& index) {
|
||||
if (!m_controller) {
|
||||
InputItem* item = m_model.itemAt(index);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (m_controller->isMenuAt(index)) {
|
||||
return;
|
||||
}
|
||||
int shortcut = m_controller->shortcutAt(index);
|
||||
int shortcut = item->shortcut();
|
||||
if (index.column() == 1) {
|
||||
m_ui.keyboardButton->click();
|
||||
} else if (index.column() == 2) {
|
||||
|
@ -76,39 +99,38 @@ void ShortcutView::load(const QModelIndex& index) {
|
|||
}
|
||||
|
||||
void ShortcutView::clear() {
|
||||
if (!m_controller) {
|
||||
return;
|
||||
}
|
||||
QModelIndex index = m_ui.shortcutTable->selectionModel()->currentIndex();
|
||||
if (m_controller->isMenuAt(index)) {
|
||||
InputItem* item = m_model.itemAt(index);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (m_ui.gamepadButton->isChecked()) {
|
||||
m_controller->clearButton(index);
|
||||
item->clearButton();
|
||||
m_ui.keyEdit->setValueButton(-1);
|
||||
} else {
|
||||
m_controller->clearKey(index);
|
||||
item->clearShortcut();
|
||||
m_ui.keyEdit->setValueKey(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutView::updateButton(int button) {
|
||||
if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) {
|
||||
InputItem* item = m_model.itemAt(m_ui.shortcutTable->selectionModel()->currentIndex());
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (m_ui.gamepadButton->isChecked()) {
|
||||
m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button);
|
||||
item->setButton(button);
|
||||
} else {
|
||||
m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), button);
|
||||
item->setShortcut(button);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutView::updateAxis(int axis, int direction) {
|
||||
if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) {
|
||||
InputItem* item = m_model.itemAt(m_ui.shortcutTable->selectionModel()->currentIndex());
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis,
|
||||
static_cast<GamepadAxisEvent::Direction>(direction));
|
||||
item->setAxis(axis, static_cast<GamepadAxisEvent::Direction>(direction));
|
||||
}
|
||||
|
||||
void ShortcutView::closeEvent(QCloseEvent*) {
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#define QGBA_SHORTCUT_VIEW
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "InputIndex.h"
|
||||
#include "InputModel.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
|
@ -15,7 +17,6 @@
|
|||
namespace QGBA {
|
||||
|
||||
class InputController;
|
||||
class InputModel;
|
||||
|
||||
class ShortcutView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -24,9 +25,11 @@ public:
|
|||
ShortcutView(QWidget* parent = nullptr);
|
||||
~ShortcutView();
|
||||
|
||||
void setModel(InputModel* controller);
|
||||
void setModel(InputIndex* model);
|
||||
void setInputController(InputController* input);
|
||||
|
||||
const InputIndex* root() { return m_model.inputIndex(); }
|
||||
|
||||
protected:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
|
@ -36,11 +39,12 @@ private slots:
|
|||
void clear();
|
||||
void updateButton(int button);
|
||||
void updateAxis(int axis, int direction);
|
||||
void updateGamepads();
|
||||
|
||||
private:
|
||||
Ui::ShortcutView m_ui;
|
||||
|
||||
InputModel* m_controller = nullptr;
|
||||
InputModel m_model;
|
||||
InputController* m_input = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -72,6 +72,27 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Current Gamepad</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="gamepadName">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
|
|
@ -90,8 +90,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
, m_logView(new LogView(&m_log))
|
||||
, m_screenWidget(new WindowBackground())
|
||||
, m_config(config)
|
||||
, m_inputModel(new InputModel(this))
|
||||
, m_inputController(m_inputModel, playerId, this)
|
||||
, m_inputController(playerId, this)
|
||||
{
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setAcceptDrops(true);
|
||||
|
@ -108,7 +107,11 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
|
||||
m_screenWidget->setMinimumSize(m_display->minimumSize());
|
||||
m_screenWidget->setSizePolicy(m_display->sizePolicy());
|
||||
int i = 2;
|
||||
#if defined(M_CORE_GBA)
|
||||
float i = 2;
|
||||
#elif defined(M_CORE_GB)
|
||||
float i = 3;
|
||||
#endif
|
||||
QVariant multiplier = m_config->getOption("scaleMultiplier");
|
||||
if (!multiplier.isNull()) {
|
||||
m_savedScale = multiplier.toInt();
|
||||
|
@ -142,8 +145,11 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
m_controller->loadGame(output, path.second, path.first);
|
||||
}
|
||||
});
|
||||
#elif defined(M_CORE_GBA)
|
||||
m_screenWidget->setSizeHint(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i));
|
||||
#endif
|
||||
#if defined(M_CORE_GBA)
|
||||
resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i));
|
||||
#elif defined(M_CORE_GB)
|
||||
resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i));
|
||||
#endif
|
||||
m_screenWidget->setPixmap(m_logo);
|
||||
m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height());
|
||||
|
@ -197,6 +203,9 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples);
|
||||
connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate);
|
||||
connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget);
|
||||
connect(&m_inputController, &InputController::keyPressed, m_controller, &GameController::keyPressed);
|
||||
connect(&m_inputController, &InputController::keyReleased, m_controller, &GameController::keyReleased);
|
||||
connect(&m_inputController, &InputController::keyAutofire, m_controller, &GameController::setAutofire);
|
||||
connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS);
|
||||
connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck);
|
||||
connect(m_display, &Display::hideCursor, [this]() {
|
||||
|
@ -212,19 +221,17 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
|
||||
m_focusCheck.setInterval(200);
|
||||
|
||||
m_inputModel->setConfigController(m_config);
|
||||
setupMenu(menuBar());
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
m_inputController.addPlatform(PLATFORM_GBA, tr("Game Boy Advance"), &GBAInputInfo);
|
||||
m_inputController.addPlatform(PLATFORM_GBA, &GBAInputInfo);
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
m_inputController.addPlatform(PLATFORM_GB, tr("Game Boy"), &GBInputInfo);
|
||||
m_inputController.addPlatform(PLATFORM_GB, &GBInputInfo);
|
||||
#endif
|
||||
#ifdef M_CORE_DS
|
||||
m_inputController.addPlatform(PLATFORM_DS, tr("DS"), &DSInputInfo);
|
||||
m_inputController.addPlatform(PLATFORM_DS, &DSInputInfo);
|
||||
#endif
|
||||
m_inputController.setupCallback(m_controller);
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
|
@ -266,9 +273,6 @@ void Window::argumentsPassed(mArguments* args) {
|
|||
|
||||
void Window::resizeFrame(const QSize& size) {
|
||||
QSize newSize(size);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
newSize /= m_screenWidget->devicePixelRatioF();
|
||||
#endif
|
||||
m_screenWidget->setSizeHint(newSize);
|
||||
newSize -= m_screenWidget->size();
|
||||
newSize += this->size();
|
||||
|
@ -504,11 +508,13 @@ void Window::exportSharkport() {
|
|||
}
|
||||
|
||||
void Window::openSettingsWindow() {
|
||||
SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_inputModel);
|
||||
SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController);
|
||||
connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS);
|
||||
connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver);
|
||||
connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
|
||||
connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);
|
||||
openView(settingsWindow);
|
||||
}
|
||||
|
||||
|
@ -806,14 +812,14 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) {
|
|||
}
|
||||
#endif
|
||||
|
||||
m_inputController.setPlatform(m_controller->platform());
|
||||
|
||||
m_hitUnimplementedBiosCall = false;
|
||||
m_fpsTimer.start();
|
||||
m_focusCheck.start();
|
||||
|
||||
m_controller->threadInterrupt();
|
||||
if (m_controller->isLoaded()) {
|
||||
m_inputController.setPlatform(m_controller->platform());
|
||||
|
||||
mCore* core = m_controller->thread()->core;
|
||||
const mCoreChannelInfo* videoLayers;
|
||||
const mCoreChannelInfo* audioChannels;
|
||||
|
@ -1010,9 +1016,9 @@ void Window::openStateWindow(LoadSave ls) {
|
|||
|
||||
void Window::setupMenu(QMenuBar* menubar) {
|
||||
menubar->clear();
|
||||
QMenu* fileMenu = menubar->addMenu(tr("&File"));
|
||||
m_inputModel->addMenu(fileMenu);
|
||||
installEventFilter(&m_inputController);
|
||||
|
||||
QMenu* fileMenu = menubar->addMenu(tr("&File"));
|
||||
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open),
|
||||
"loadROM");
|
||||
#ifdef USE_SQLITE3
|
||||
|
@ -1069,8 +1075,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));
|
||||
QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));
|
||||
m_inputModel->addMenu(quickLoadMenu);
|
||||
m_inputModel->addMenu(quickSaveMenu);
|
||||
|
||||
QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu);
|
||||
connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState);
|
||||
|
@ -1162,7 +1166,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
#endif
|
||||
|
||||
QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));
|
||||
m_inputModel->addMenu(emulationMenu);
|
||||
QAction* reset = new QAction(tr("&Reset"), emulationMenu);
|
||||
reset->setShortcut(tr("Ctrl+R"));
|
||||
connect(reset, &QAction::triggered, m_controller, &GameController::reset);
|
||||
|
@ -1203,11 +1206,11 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
emulationMenu->addSeparator();
|
||||
|
||||
m_inputModel->addFunctions(emulationMenu, [this]() {
|
||||
m_inputController.inputIndex()->addItem(qMakePair([this]() {
|
||||
m_controller->setTurbo(true, false);
|
||||
}, [this]() {
|
||||
m_controller->setTurbo(false, false);
|
||||
}, QKeySequence(Qt::Key_Tab), tr("Fast forward (held)"), "holdFastForward");
|
||||
}), tr("Fast forward (held)"), "holdFastForward", emulationMenu)->setShortcut(QKeySequence(Qt::Key_Tab)[0]);
|
||||
|
||||
QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu);
|
||||
turbo->setCheckable(true);
|
||||
|
@ -1229,11 +1232,11 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
}
|
||||
m_config->updateOption("fastForwardRatio");
|
||||
|
||||
m_inputModel->addFunctions(emulationMenu, [this]() {
|
||||
m_inputController.inputIndex()->addItem(qMakePair([this]() {
|
||||
m_controller->startRewinding();
|
||||
}, [this]() {
|
||||
m_controller->stopRewinding();
|
||||
}, QKeySequence("`"), tr("Rewind (held)"), "holdRewind");
|
||||
}), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]);
|
||||
|
||||
QAction* rewind = new QAction(tr("Re&wind"), emulationMenu);
|
||||
rewind->setShortcut(tr("~"));
|
||||
|
@ -1270,7 +1273,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
emulationMenu->addSeparator();
|
||||
|
||||
QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor"));
|
||||
m_inputModel->addMenu(solarMenu);
|
||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
|
||||
connect(solarIncrease, &QAction::triggered, m_controller, &GameController::increaseLuminanceLevel);
|
||||
addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
|
||||
|
@ -1297,9 +1299,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
}
|
||||
|
||||
QMenu* avMenu = menubar->addMenu(tr("Audio/&Video"));
|
||||
m_inputModel->addMenu(avMenu);
|
||||
QMenu* frameMenu = avMenu->addMenu(tr("Frame size"));
|
||||
m_inputModel->addMenu(frameMenu, avMenu);
|
||||
for (int i = 1; i <= 6; ++i) {
|
||||
QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu);
|
||||
setSize->setCheckable(true);
|
||||
|
@ -1434,13 +1434,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
avMenu->addSeparator();
|
||||
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
||||
m_inputModel->addMenu(m_videoLayers, avMenu);
|
||||
|
||||
m_audioChannels = avMenu->addMenu(tr("Audio channels"));
|
||||
m_inputModel->addMenu(m_audioChannels, avMenu);
|
||||
|
||||
QMenu* toolsMenu = menubar->addMenu(tr("&Tools"));
|
||||
m_inputModel->addMenu(toolsMenu);
|
||||
QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu);
|
||||
connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show);
|
||||
addControlledAction(toolsMenu, viewLogs, "viewLogs");
|
||||
|
@ -1583,72 +1579,11 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
exitFullScreen->setShortcut(QKeySequence("Esc"));
|
||||
addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen");
|
||||
|
||||
QMenu* autofireMenu = new QMenu(tr("Autofire"), this);
|
||||
m_inputModel->addMenu(autofireMenu);
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_A, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_A, false);
|
||||
}, QKeySequence(), tr("Autofire A"), "autofireA");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_B, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_B, false);
|
||||
}, QKeySequence(), tr("Autofire B"), "autofireB");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_L, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_L, false);
|
||||
}, QKeySequence(), tr("Autofire L"), "autofireL");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_R, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_R, false);
|
||||
}, QKeySequence(), tr("Autofire R"), "autofireR");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_START, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_START, false);
|
||||
}, QKeySequence(), tr("Autofire Start"), "autofireStart");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_SELECT, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_SELECT, false);
|
||||
}, QKeySequence(), tr("Autofire Select"), "autofireSelect");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_UP, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_UP, false);
|
||||
}, QKeySequence(), tr("Autofire Up"), "autofireUp");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_RIGHT, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_RIGHT, false);
|
||||
}, QKeySequence(), tr("Autofire Right"), "autofireRight");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_DOWN, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_DOWN, false);
|
||||
}, QKeySequence(), tr("Autofire Down"), "autofireDown");
|
||||
|
||||
m_inputModel->addFunctions(autofireMenu, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_LEFT, true);
|
||||
}, [this]() {
|
||||
m_controller->setAutofire(GBA_KEY_LEFT, false);
|
||||
}, QKeySequence(), tr("Autofire Left"), "autofireLeft");
|
||||
|
||||
for (QAction* action : m_gameActions) {
|
||||
action->setDisabled(true);
|
||||
}
|
||||
|
||||
m_inputController.rebuildIndex();
|
||||
}
|
||||
|
||||
void Window::attachWidget(QWidget* widget) {
|
||||
|
@ -1701,7 +1636,7 @@ QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString
|
|||
}
|
||||
|
||||
QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) {
|
||||
m_inputModel->addAction(menu, action, name);
|
||||
m_inputController.inputIndex()->addItem(action, name, menu);
|
||||
action->setShortcutContext(Qt::WidgetShortcut);
|
||||
addAction(action);
|
||||
return action;
|
||||
|
|
|
@ -28,7 +28,6 @@ class Display;
|
|||
class GameController;
|
||||
class GDBController;
|
||||
class GIFView;
|
||||
class InputModel;
|
||||
class LibraryController;
|
||||
class LogView;
|
||||
class ShaderSelector;
|
||||
|
@ -171,7 +170,6 @@ private:
|
|||
WindowBackground* m_screenWidget;
|
||||
QPixmap m_logo{":/res/medusa-bg.png"};
|
||||
ConfigController* m_config;
|
||||
InputModel* m_inputModel;
|
||||
InputController m_inputController;
|
||||
QList<QDateTime> m_frameList;
|
||||
QTimer m_fpsTimer;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "GameController.h"
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputItem.h"
|
||||
#include "InputModel.h"
|
||||
#include "InputProfile.h"
|
||||
|
||||
|
@ -30,6 +31,7 @@
|
|||
#ifdef M_CORE_DS
|
||||
#include <mgba/internal/ds/input.h>
|
||||
#endif
|
||||
#include <initializer_list>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -38,13 +40,13 @@ int InputController::s_sdlInited = 0;
|
|||
mSDLEvents InputController::s_sdlEvents;
|
||||
#endif
|
||||
|
||||
InputController::InputController(InputModel* model, int playerId, QWidget* topLevel, QObject* parent)
|
||||
InputController::InputController(int playerId, QWidget* topLevel, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_inputModel(model)
|
||||
, m_platform(PLATFORM_NONE)
|
||||
, m_playerId(playerId)
|
||||
, m_topLevel(topLevel)
|
||||
, m_focusParent(topLevel)
|
||||
, m_bindings(new QMenu(tr("Controls")))
|
||||
, m_autofire(new QMenu(tr("Autofire")))
|
||||
{
|
||||
#ifdef BUILD_SDL
|
||||
if (s_sdlInited == 0) {
|
||||
|
@ -65,20 +67,74 @@ InputController::InputController(InputModel* model, int playerId, QWidget* topLe
|
|||
m_gamepadTimer.setInterval(50);
|
||||
m_gamepadTimer.start();
|
||||
|
||||
m_autofireMenu = std::unique_ptr<QMenu>(new QMenu(tr("Autofire")));
|
||||
m_inputModel->addMenu(m_autofireMenu.get());
|
||||
static QList<QPair<QString, int>> defaultBindings({
|
||||
qMakePair(QLatin1String("A"), Qt::Key_Z),
|
||||
qMakePair(QLatin1String("B"), Qt::Key_X),
|
||||
qMakePair(QLatin1String("L"), Qt::Key_A),
|
||||
qMakePair(QLatin1String("R"), Qt::Key_S),
|
||||
qMakePair(QLatin1String("Start"), Qt::Key_Return),
|
||||
qMakePair(QLatin1String("Select"), Qt::Key_Backspace),
|
||||
qMakePair(QLatin1String("Up"), Qt::Key_Up),
|
||||
qMakePair(QLatin1String("Down"), Qt::Key_Down),
|
||||
qMakePair(QLatin1String("Left"), Qt::Key_Left),
|
||||
qMakePair(QLatin1String("Right"), Qt::Key_Right)
|
||||
});
|
||||
|
||||
m_inputMenu = std::unique_ptr<QMenu>(new QMenu(tr("Bindings")));
|
||||
m_inputModel->addMenu(m_inputMenu.get());
|
||||
for (auto k : defaultBindings) {
|
||||
addKey(k.first);
|
||||
}
|
||||
m_keyIndex.rebuild();
|
||||
for (auto k : defaultBindings) {
|
||||
bindKey(KEYBOARD, k.second, k.first);
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_inputModel, SIGNAL(keyRebound(const QModelIndex&, int)), this, SLOT(bindKey(const QModelIndex&, int)));
|
||||
connect(m_inputModel, SIGNAL(buttonRebound(const QModelIndex&, int)), this, SLOT(bindButton(const QModelIndex&, int)));
|
||||
connect(m_inputModel, SIGNAL(axisRebound(const QModelIndex&, int, GamepadAxisEvent::Direction)), this, SLOT(bindAxis(const QModelIndex&, int, GamepadAxisEvent::Direction)));
|
||||
void InputController::addKey(const QString& name) {
|
||||
if (itemForKey(name)) {
|
||||
return;
|
||||
}
|
||||
m_keyIndex.addItem(qMakePair([this, name]() {
|
||||
emit keyPressed(keyId(name));
|
||||
}, [this, name]() {
|
||||
emit keyReleased(keyId(name));
|
||||
}), name, QString("key%0").arg(name), m_bindings.get());
|
||||
|
||||
m_keyIndex.addItem(qMakePair([this, name]() {
|
||||
emit keyAutofire(keyId(name), true);
|
||||
}, [this, name]() {
|
||||
emit keyAutofire(keyId(name), false);
|
||||
}), name, QString("autofire%1").arg(name), m_autofire.get());
|
||||
}
|
||||
|
||||
void InputController::addPlatform(mPlatform platform, const mInputPlatformInfo* info) {
|
||||
m_keyInfo[platform] = info;
|
||||
for (size_t i = 0; i < info->nKeys; ++i) {
|
||||
addKey(info->keyId[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void InputController::setPlatform(mPlatform platform) {
|
||||
if (m_activeKeyInfo) {
|
||||
mInputMapDeinit(&m_inputMap);
|
||||
}
|
||||
|
||||
m_sdlPlayer.bindings = &m_inputMap;
|
||||
m_activeKeyInfo = m_keyInfo[platform];
|
||||
mInputMapInit(&m_inputMap, m_activeKeyInfo);
|
||||
|
||||
loadConfiguration(KEYBOARD);
|
||||
#ifdef BUILD_SDL
|
||||
mSDLInitBindingsGBA(&m_inputMap);
|
||||
loadConfiguration(SDL_BINDING_BUTTON);
|
||||
#endif
|
||||
|
||||
rebuildKeyIndex();
|
||||
restoreModel();
|
||||
}
|
||||
|
||||
InputController::~InputController() {
|
||||
for (auto& inputMap : m_inputMaps) {
|
||||
mInputMapDeinit(&inputMap);
|
||||
if (m_activeKeyInfo) {
|
||||
mInputMapDeinit(&m_inputMap);
|
||||
}
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
|
@ -93,82 +149,27 @@ InputController::~InputController() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void InputController::addPlatform(mPlatform platform, const QString& visibleName, const mInputPlatformInfo* info) {
|
||||
mInputMap* inputMap = &m_inputMaps[platform];
|
||||
mInputMapInit(inputMap, info);
|
||||
|
||||
QMenu* input = m_inputMenu->addMenu(visibleName);
|
||||
QMenu* autofire = m_autofireMenu->addMenu(visibleName);
|
||||
m_inputMenuIndices[platform] = m_inputModel->addMenu(input, m_inputMenu.get());
|
||||
m_inputModel->addMenu(autofire, m_autofireMenu.get());
|
||||
|
||||
for (size_t i = 0; i < info->nKeys; ++i) {
|
||||
m_inputModel->addKey(input, platform, i, 0, info->keyId[i], QString("%1.%2").arg(info->platformName).arg(info->keyId[i]));
|
||||
m_inputModel->addKey(autofire, platform, i, 0, info->keyId[i], QString("%1.autofire.%2").arg(info->platformName).arg(info->keyId[i]));
|
||||
}
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
mSDLInitBindingsGBA(inputMap);
|
||||
#endif
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case PLATFORM_GBA:
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, GB_KEY_A);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, GB_KEY_B);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, GB_KEY_START);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, GB_KEY_SELECT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, GB_KEY_UP);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, GB_KEY_DOWN);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, GB_KEY_LEFT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, GB_KEY_RIGHT);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_DS
|
||||
case PLATFORM_DS:
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_X, DS_KEY_A);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Z, DS_KEY_B);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_A, DS_KEY_X);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_S, DS_KEY_Y);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Q, DS_KEY_L);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_W, DS_KEY_R);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Return, DS_KEY_START);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Backspace, DS_KEY_SELECT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Up, DS_KEY_UP);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Down, DS_KEY_DOWN);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Left, DS_KEY_LEFT);
|
||||
mInputBindKey(inputMap, KEYBOARD, Qt::Key_Right, DS_KEY_RIGHT);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
void InputController::rebuildIndex(const InputIndex* index) {
|
||||
m_inputIndex.rebuild(index);
|
||||
}
|
||||
|
||||
void InputController::setPlatform(mPlatform platform) {
|
||||
#ifdef BUILD_SDL
|
||||
m_sdlPlayer.bindings = &m_inputMaps[platform];
|
||||
#endif
|
||||
m_platform = platform;
|
||||
void InputController::rebuildKeyIndex(const InputIndex* index) {
|
||||
m_keyIndex.rebuild(index);
|
||||
|
||||
for (const InputItem* item : m_keyIndex.items()) {
|
||||
if (!item->name().startsWith(QLatin1String("key"))) {
|
||||
rebindKey(item->visibleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputController::setConfiguration(ConfigController* config) {
|
||||
m_config = config;
|
||||
m_inputIndex.setConfigController(config);
|
||||
m_keyIndex.setConfigController(config);
|
||||
setAllowOpposing(config->getOption("allowOpposingDirections").toInt());
|
||||
loadConfiguration(KEYBOARD);
|
||||
loadProfile(KEYBOARD, profileForType(KEYBOARD));
|
||||
#ifdef BUILD_SDL
|
||||
mSDLEventsLoadConfig(&s_sdlEvents, config->input());
|
||||
if (!m_playerAttached) {
|
||||
|
@ -181,31 +182,23 @@ void InputController::setConfiguration(ConfigController* config) {
|
|||
}
|
||||
|
||||
void InputController::loadConfiguration(uint32_t type) {
|
||||
for (auto& inputMap : m_inputMaps) {
|
||||
mInputMapLoad(&inputMap, type, m_config->input());
|
||||
#ifdef BUILD_SDL
|
||||
if (m_playerAttached) {
|
||||
mInputMap* bindings = m_sdlPlayer.bindings;
|
||||
m_sdlPlayer.bindings = &inputMap;
|
||||
mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
|
||||
m_sdlPlayer.bindings = bindings;
|
||||
}
|
||||
#endif
|
||||
if (!m_activeKeyInfo) {
|
||||
return;
|
||||
}
|
||||
mInputMapLoad(&m_inputMap, type, m_config->input());
|
||||
#ifdef BUILD_SDL
|
||||
if (m_playerAttached) {
|
||||
mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
||||
for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) {
|
||||
bool loaded = mInputProfileLoad(&iter.value(), type, m_config->input(), profile.toUtf8().constData());
|
||||
if (!loaded) {
|
||||
const InputProfile* ip = InputProfile::findProfile(iter.key(), profile);
|
||||
if (ip) {
|
||||
ip->apply(iter.key(), this);
|
||||
}
|
||||
}
|
||||
const InputProfile* ip = InputProfile::findProfile(profile);
|
||||
if (ip) {
|
||||
ip->apply(this);
|
||||
}
|
||||
recalibrateAxes();
|
||||
m_inputModel->loadProfile(PLATFORM_NONE, profile); // TODO
|
||||
emit profileLoaded(profile);
|
||||
}
|
||||
|
||||
|
@ -217,23 +210,22 @@ void InputController::saveConfiguration() {
|
|||
if (m_playerAttached) {
|
||||
mSDLPlayerSaveConfig(&m_sdlPlayer, m_config->input());
|
||||
}
|
||||
m_config->write();
|
||||
#endif
|
||||
m_inputIndex.saveConfig();
|
||||
m_keyIndex.saveConfig();
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
void InputController::saveConfiguration(uint32_t type) {
|
||||
for (auto& inputMap : m_inputMaps) {
|
||||
if (!inputMap.info) {
|
||||
continue;
|
||||
}
|
||||
mInputMapSave(&inputMap, type, m_config->input());
|
||||
if (m_activeKeyInfo) {
|
||||
mInputMapSave(&m_inputMap, type, m_config->input());
|
||||
}
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
void InputController::saveProfile(uint32_t type, const QString& profile) {
|
||||
for (auto& inputMap : m_inputMaps) {
|
||||
mInputProfileSave(&inputMap, type, m_config->input(), profile.toUtf8().constData());
|
||||
if (m_activeKeyInfo) {
|
||||
mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
||||
}
|
||||
m_config->write();
|
||||
}
|
||||
|
@ -383,7 +375,7 @@ void InputController::updateJoysticks() {
|
|||
}
|
||||
|
||||
const mInputMap* InputController::map() {
|
||||
return &m_inputMaps[m_platform];
|
||||
return &m_inputMap;
|
||||
}
|
||||
|
||||
int InputController::pollEvents() {
|
||||
|
@ -395,7 +387,7 @@ int InputController::pollEvents() {
|
|||
int numButtons = SDL_JoystickNumButtons(joystick);
|
||||
int i;
|
||||
for (i = 0; i < numButtons; ++i) {
|
||||
GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i));
|
||||
GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i));
|
||||
if (key == GBA_KEY_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
@ -409,14 +401,14 @@ int InputController::pollEvents() {
|
|||
int numHats = SDL_JoystickNumHats(joystick);
|
||||
for (i = 0; i < numHats; ++i) {
|
||||
int hat = SDL_JoystickGetHat(joystick, i);
|
||||
activeButtons |= mInputMapHat(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, hat);
|
||||
activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat);
|
||||
}
|
||||
|
||||
int numAxes = SDL_JoystickNumAxes(joystick);
|
||||
for (i = 0; i < numAxes; ++i) {
|
||||
int value = SDL_JoystickGetAxis(joystick, i);
|
||||
|
||||
enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMaps[m_platform], SDL_BINDING_BUTTON, i, value));
|
||||
enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value));
|
||||
if (key != GBA_KEY_NONE) {
|
||||
activeButtons |= 1 << key;
|
||||
}
|
||||
|
@ -486,31 +478,28 @@ QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes
|
|||
return activeAxes;
|
||||
}
|
||||
|
||||
void InputController::bindKey(mPlatform platform, uint32_t type, int key, int coreKey) {
|
||||
if (m_inputMaps.find(platform) == m_inputMaps.end() || coreKey >= m_inputMaps[platform].info->nKeys) {
|
||||
return;
|
||||
}
|
||||
QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]);
|
||||
bool signalsBlocked = m_inputModel->blockSignals(true);
|
||||
void InputController::bindKey(uint32_t type, int key, const QString& keyName) {
|
||||
InputItem* item = itemForKey(keyName);
|
||||
if (type != KEYBOARD) {
|
||||
m_inputModel->updateButton(index, key);
|
||||
item->setButton(key);
|
||||
} else {
|
||||
m_inputModel->updateKey(index, key);
|
||||
item->setShortcut(key);
|
||||
}
|
||||
if (m_activeKeyInfo) {
|
||||
int coreKey = keyId(keyName);
|
||||
mInputBindKey(&m_inputMap, type, key, coreKey);
|
||||
}
|
||||
m_inputModel->blockSignals(signalsBlocked);
|
||||
mInputBindKey(&m_inputMaps[platform], type, key, coreKey);
|
||||
}
|
||||
|
||||
void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction direction, int key) {
|
||||
if (m_inputMaps.find(platform) == m_inputMaps.end() || key >= m_inputMaps[platform].info->nKeys) {
|
||||
void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, const QString& key) {
|
||||
InputItem* item = itemForKey(key);
|
||||
item->setAxis(axis, direction);
|
||||
|
||||
if (!m_activeKeyInfo) {
|
||||
return;
|
||||
}
|
||||
QModelIndex index = m_inputModel->index(key, 0, m_inputMenuIndices[platform]);
|
||||
bool signalsBlocked = m_inputModel->blockSignals(true);
|
||||
m_inputModel->updateAxis(index, axis, direction);
|
||||
m_inputModel->blockSignals(signalsBlocked);
|
||||
|
||||
const mInputAxis* old = mInputQueryAxis(&m_inputMaps[platform], type, axis);
|
||||
const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis);
|
||||
mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
|
||||
if (old) {
|
||||
description = *old;
|
||||
|
@ -521,18 +510,18 @@ void InputController::bindAxis(mPlatform platform, uint32_t type, int axis, Game
|
|||
}
|
||||
switch (direction) {
|
||||
case GamepadAxisEvent::NEGATIVE:
|
||||
description.lowDirection = key;
|
||||
description.lowDirection = keyId(key);
|
||||
|
||||
description.deadLow = deadzone - AXIS_THRESHOLD;
|
||||
break;
|
||||
case GamepadAxisEvent::POSITIVE:
|
||||
description.highDirection = key;
|
||||
description.highDirection = keyId(key);
|
||||
description.deadHigh = deadzone + AXIS_THRESHOLD;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
mInputBindAxis(&m_inputMaps[platform], type, axis, &description);
|
||||
mInputBindAxis(&m_inputMap, type, axis, &description);
|
||||
}
|
||||
|
||||
QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {
|
||||
|
@ -567,32 +556,30 @@ QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(
|
|||
return activeHats;
|
||||
}
|
||||
|
||||
void InputController::bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction direction, int coreKey) {
|
||||
if (m_inputMaps.find(platform) == m_inputMaps.end() || coreKey >= m_inputMaps[platform].info->nKeys) {
|
||||
void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, const QString& key) {
|
||||
if (!m_activeKeyInfo) {
|
||||
return;
|
||||
}
|
||||
QModelIndex index = m_inputModel->index(coreKey, 0, m_inputMenuIndices[platform]);
|
||||
//m_inputModel->updateHat(index, hat, direction);
|
||||
|
||||
mInputHatBindings bindings{ -1, -1, -1, -1 };
|
||||
mInputQueryHat(&m_inputMaps[platform], type, hat, &bindings);
|
||||
mInputQueryHat(&m_inputMap, type, hat, &bindings);
|
||||
switch (direction) {
|
||||
case GamepadHatEvent::UP:
|
||||
bindings.up = coreKey;
|
||||
bindings.up = keyId(key);
|
||||
break;
|
||||
case GamepadHatEvent::RIGHT:
|
||||
bindings.right = coreKey;
|
||||
bindings.right = keyId(key);
|
||||
break;
|
||||
case GamepadHatEvent::DOWN:
|
||||
bindings.down = coreKey;
|
||||
bindings.down = keyId(key);
|
||||
break;
|
||||
case GamepadHatEvent::LEFT:
|
||||
bindings.left = coreKey;
|
||||
bindings.left = keyId(key);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
mInputBindHat(&m_inputMaps[platform], type, hat, &bindings);
|
||||
mInputBindHat(&m_inputMap, type, hat, &bindings);
|
||||
}
|
||||
|
||||
void InputController::testGamepad(int type) {
|
||||
|
@ -730,70 +717,15 @@ void InputController::releaseFocus(QWidget* focus) {
|
|||
}
|
||||
}
|
||||
|
||||
void InputController::setupCallback(GameController* controller) {
|
||||
m_inputModel->setKeyCallback([this, controller](QMenu* menu, int key, bool down) {
|
||||
if (menu->parent() == m_autofireMenu.get()) {
|
||||
controller->setAutofire(key, down);
|
||||
} else {
|
||||
if (down) {
|
||||
controller->keyPressed(key);
|
||||
} else {
|
||||
controller->keyReleased(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InputController::bindKey(const QModelIndex& index, int key) {
|
||||
int coreKey = m_inputModel->keyAt(index);
|
||||
if (coreKey < 0) {
|
||||
return;
|
||||
}
|
||||
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
|
||||
bindKey(platform, KEYBOARD, key, coreKey);
|
||||
}
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
void InputController::bindButton(const QModelIndex& index, int key) {
|
||||
int coreKey = m_inputModel->keyAt(index);
|
||||
if (coreKey < 0) {
|
||||
return;
|
||||
}
|
||||
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
|
||||
bindKey(platform, SDL_BINDING_BUTTON, key, coreKey);
|
||||
}
|
||||
|
||||
void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
|
||||
int coreKey = m_inputModel->keyAt(index);
|
||||
if (coreKey < 0) {
|
||||
return;
|
||||
}
|
||||
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
|
||||
bindAxis(platform, SDL_BINDING_BUTTON, axis, direction, coreKey);
|
||||
}
|
||||
|
||||
void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction direction) {
|
||||
int coreKey = m_inputModel->keyAt(index);
|
||||
if (coreKey < 0) {
|
||||
return;
|
||||
}
|
||||
mPlatform platform = m_inputMenuIndices.key(index.parent(), PLATFORM_NONE);
|
||||
bindHat(platform, SDL_BINDING_BUTTON, hat, direction, coreKey);
|
||||
}
|
||||
#else
|
||||
void InputController::bindButton(const QModelIndex& index, int key) {}
|
||||
void InputController::bindAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction) {}
|
||||
void InputController::bindHat(const QModelIndex& index, int hat, GamepadHatEvent::Direction) {}
|
||||
#endif
|
||||
|
||||
bool InputController::eventFilter(QObject*, QEvent* event) {
|
||||
event->ignore();
|
||||
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
if (!InputModel::isModifierKey(key)) {
|
||||
if (!InputIndex::isModifierKey(key)) {
|
||||
key |= (keyEvent->modifiers() & ~Qt::KeypadModifier);
|
||||
} else {
|
||||
key = InputModel::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
|
||||
key = InputIndex::toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));
|
||||
}
|
||||
|
||||
if (keyEvent->isAutoRepeat()) {
|
||||
|
@ -801,67 +733,120 @@ bool InputController::eventFilter(QObject*, QEvent* event) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (m_inputModel->triggerKey(key, event->type() == QEvent::KeyPress, m_platform)) {
|
||||
event->ignore();
|
||||
InputItem* item = m_inputIndex.itemForShortcut(key);
|
||||
if (item) {
|
||||
item->trigger(event->type() == QEvent::KeyPress);
|
||||
event->accept();
|
||||
}
|
||||
item = m_keyIndex.itemForShortcut(key);
|
||||
if (item) {
|
||||
item->trigger(event->type() == QEvent::KeyPress);
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadButtonEvent::Up()) {
|
||||
GamepadButtonEvent* gbe = static_cast<GamepadButtonEvent*>(event);
|
||||
if (m_inputModel->triggerButton(gbe->value(), event->type() == GamepadButtonEvent::Down())) {
|
||||
InputItem* item = m_inputIndex.itemForButton(gbe->value());
|
||||
if (item) {
|
||||
item->trigger(event->type() == GamepadButtonEvent::Down());
|
||||
event->accept();
|
||||
}
|
||||
item = m_keyIndex.itemForButton(gbe->value());
|
||||
if (item) {
|
||||
item->trigger(event->type() == GamepadButtonEvent::Down());
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (event->type() == GamepadAxisEvent::Type()) {
|
||||
GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
|
||||
if (m_inputModel->triggerAxis(gae->axis(), gae->direction(), gae->isNew())) {
|
||||
InputItem* item = m_inputIndex.itemForAxis(gae->axis(), gae->direction());
|
||||
if (item) {
|
||||
item->trigger(event->type() == gae->isNew());
|
||||
event->accept();
|
||||
}
|
||||
item = m_keyIndex.itemForAxis(gae->axis(), gae->direction());
|
||||
if (item) {
|
||||
item->trigger(event->type() == gae->isNew());
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return event->isAccepted();
|
||||
}
|
||||
|
||||
InputItem* InputController::itemForKey(const QString& key) {
|
||||
return m_keyIndex.itemAt(QString("key%0").arg(key));
|
||||
}
|
||||
|
||||
int InputController::keyId(const QString& key) {
|
||||
for (int i = 0; i < m_activeKeyInfo->nKeys; ++i) {
|
||||
if (m_activeKeyInfo->keyId[i] == key) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InputController::restoreModel() {
|
||||
bool signalsBlocked = m_inputModel->blockSignals(true);
|
||||
for (auto iter = m_inputMaps.begin(); iter != m_inputMaps.end(); ++iter) {
|
||||
mPlatform platform = iter.key();
|
||||
QModelIndex parent = m_inputMenuIndices[platform];
|
||||
int nKeys = iter->info->nKeys;
|
||||
for (int i = 0; i < nKeys; ++i) {
|
||||
int key = mInputQueryBinding(&iter.value(), KEYBOARD, i);
|
||||
if (!m_activeKeyInfo) {
|
||||
return;
|
||||
}
|
||||
int nKeys = m_inputMap.info->nKeys;
|
||||
for (int i = 0; i < nKeys; ++i) {
|
||||
const QString& keyName = m_inputMap.info->keyId[i];
|
||||
InputItem* item = itemForKey(keyName);
|
||||
if (item) {
|
||||
int key = mInputQueryBinding(&m_inputMap, KEYBOARD, i);
|
||||
if (key >= 0) {
|
||||
m_inputModel->updateKey(m_inputModel->index(i, 0, parent), key);
|
||||
item->setShortcut(key);
|
||||
} else {
|
||||
m_inputModel->clearKey(m_inputModel->index(i, 0, parent));
|
||||
item->clearShortcut();
|
||||
}
|
||||
#ifdef BUILD_SDL
|
||||
key = mInputQueryBinding(&iter.value(), SDL_BINDING_BUTTON, i);
|
||||
key = mInputQueryBinding(&m_inputMap, SDL_BINDING_BUTTON, i);
|
||||
if (key >= 0) {
|
||||
m_inputModel->updateButton(m_inputModel->index(i, 0, parent), key);
|
||||
item->setButton(key);
|
||||
} else {
|
||||
m_inputModel->clearButton(m_inputModel->index(i, 0, parent));
|
||||
item->clearButton();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef BUILD_SDL
|
||||
struct Context {
|
||||
InputModel* model;
|
||||
QModelIndex parent;
|
||||
} context{ m_inputModel, parent };
|
||||
mInputEnumerateAxes(&iter.value(), SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) {
|
||||
Context* context = static_cast<Context*>(user);
|
||||
if (description->highDirection >= 0) {
|
||||
context->model->updateAxis(context->model->index(description->highDirection, 0, context->parent), axis, GamepadAxisEvent::POSITIVE);
|
||||
}
|
||||
if (description->lowDirection >= 0) {
|
||||
context->model->updateAxis(context->model->index(description->lowDirection, 0, context->parent), axis, GamepadAxisEvent::NEGATIVE);
|
||||
}
|
||||
}, &context);
|
||||
#endif
|
||||
}
|
||||
m_inputModel->blockSignals(signalsBlocked);
|
||||
#ifdef BUILD_SDL
|
||||
mInputEnumerateAxes(&m_inputMap, SDL_BINDING_BUTTON, [](int axis, const struct mInputAxis* description, void* user) {
|
||||
InputController* controller = static_cast<InputController*>(user);
|
||||
InputItem* item;
|
||||
const mInputPlatformInfo* inputMap = controller->m_inputMap.info;
|
||||
if (description->highDirection >= 0 && description->highDirection < controller->m_inputMap.info->nKeys) {
|
||||
int id = description->lowDirection;
|
||||
if (id >= 0 && id < inputMap->nKeys) {
|
||||
item = controller->itemForKey(inputMap->keyId[id]);
|
||||
if (item) {
|
||||
item->setAxis(axis, GamepadAxisEvent::POSITIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (description->lowDirection >= 0 && description->lowDirection < controller->m_inputMap.info->nKeys) {
|
||||
int id = description->highDirection;
|
||||
if (id >= 0 && id < inputMap->nKeys) {
|
||||
item = controller->itemForKey(inputMap->keyId[id]);
|
||||
if (item) {
|
||||
item->setAxis(axis, GamepadAxisEvent::NEGATIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
#endif
|
||||
rebuildKeyIndex();
|
||||
}
|
||||
|
||||
void InputController::rebindKey(const QString& key) {
|
||||
InputItem* item = itemForKey(key);
|
||||
bindKey(KEYBOARD, item->shortcut(), key);
|
||||
#ifdef BUILD_SDL
|
||||
bindKey(SDL_BINDING_BUTTON, item->button(), key);
|
||||
bindAxis(SDL_BINDING_BUTTON, item->axis(), item->direction(), key);
|
||||
#endif
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadHatEvent.h"
|
||||
#include "InputIndex.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -33,7 +34,7 @@ namespace QGBA {
|
|||
|
||||
class ConfigController;
|
||||
class GameController;
|
||||
class InputModel;
|
||||
class InputItem;
|
||||
|
||||
class InputController : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -41,11 +42,17 @@ Q_OBJECT
|
|||
public:
|
||||
static const uint32_t KEYBOARD = 0x51545F4B;
|
||||
|
||||
InputController(InputModel* model, int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
|
||||
InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);
|
||||
~InputController();
|
||||
|
||||
void addPlatform(mPlatform, const QString& visibleName, const mInputPlatformInfo*);
|
||||
InputIndex* inputIndex() { return &m_inputIndex; }
|
||||
InputIndex* keyIndex() { return &m_keyIndex; }
|
||||
void rebuildIndex(const InputIndex* = nullptr);
|
||||
void rebuildKeyIndex(const InputIndex* = nullptr);
|
||||
|
||||
void addPlatform(mPlatform, const mInputPlatformInfo*);
|
||||
void setPlatform(mPlatform);
|
||||
void addKey(const QString& name);
|
||||
|
||||
void setConfiguration(ConfigController* config);
|
||||
void saveConfiguration();
|
||||
|
@ -68,9 +75,9 @@ public:
|
|||
QSet<QPair<int, GamepadHatEvent::Direction>> activeGamepadHats(int type);
|
||||
void recalibrateAxes();
|
||||
|
||||
void bindKey(mPlatform platform, uint32_t type, int key, int);
|
||||
void bindAxis(mPlatform platform, uint32_t type, int axis, GamepadAxisEvent::Direction, int);
|
||||
void bindHat(mPlatform platform, uint32_t type, int hat, GamepadHatEvent::Direction, int);
|
||||
void bindKey(uint32_t type, int key, const QString&);
|
||||
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, const QString&);
|
||||
void bindHat(uint32_t type, int hat, GamepadHatEvent::Direction, const QString&);
|
||||
|
||||
QStringList connectedGamepads(uint32_t type) const;
|
||||
int gamepad(uint32_t type) const;
|
||||
|
@ -91,10 +98,11 @@ public:
|
|||
mRumble* rumble();
|
||||
mRotationSource* rotationSource();
|
||||
|
||||
void setupCallback(GameController* controller);
|
||||
|
||||
signals:
|
||||
void profileLoaded(const QString& profile);
|
||||
void keyPressed(int);
|
||||
void keyReleased(int);
|
||||
void keyAutofire(int, bool enabled);
|
||||
|
||||
public slots:
|
||||
void testGamepad(int type);
|
||||
|
@ -105,12 +113,6 @@ public slots:
|
|||
void resumeScreensaver();
|
||||
void setScreensaverSuspendable(bool);
|
||||
|
||||
private slots:
|
||||
void bindKey(const QModelIndex&, int key);
|
||||
void bindButton(const QModelIndex&, int key);
|
||||
void bindAxis(const QModelIndex&, int axis, GamepadAxisEvent::Direction);
|
||||
void bindHat(const QModelIndex&, int hat, GamepadHatEvent::Direction);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
|
@ -120,15 +122,24 @@ private:
|
|||
bool hasPendingEvent(int key) const;
|
||||
void sendGamepadEvent(QEvent*);
|
||||
void restoreModel();
|
||||
void rebindKey(const QString& key);
|
||||
|
||||
InputModel* m_inputModel;
|
||||
mPlatform m_platform;
|
||||
QMap<mPlatform, mInputMap> m_inputMaps;
|
||||
InputItem* itemForKey(const QString& key);
|
||||
int keyId(const QString& key);
|
||||
|
||||
InputIndex m_inputIndex;
|
||||
InputIndex m_keyIndex;
|
||||
mInputMap m_inputMap;
|
||||
ConfigController* m_config = nullptr;
|
||||
int m_playerId;
|
||||
bool m_allowOpposing = false;
|
||||
QWidget* m_topLevel;
|
||||
QWidget* m_focusParent;
|
||||
QMap<mPlatform, const mInputPlatformInfo*> m_keyInfo;
|
||||
const mInputPlatformInfo* m_activeKeyInfo = nullptr;
|
||||
|
||||
std::unique_ptr<QMenu> m_bindings;
|
||||
std::unique_ptr<QMenu> m_autofire;
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
static int s_sdlInited;
|
||||
|
@ -139,10 +150,6 @@ private:
|
|||
|
||||
QVector<int> m_deadzones;
|
||||
|
||||
std::unique_ptr<QMenu> m_inputMenu;
|
||||
std::unique_ptr<QMenu> m_autofireMenu;
|
||||
QMap<mPlatform, QModelIndex> m_inputMenuIndices;
|
||||
|
||||
QSet<int> m_activeButtons;
|
||||
QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes;
|
||||
QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats;
|
|
@ -0,0 +1,250 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputIndex.h"
|
||||
|
||||
#include "ConfigController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
void InputIndex::setConfigController(ConfigController* controller) {
|
||||
m_config = controller;
|
||||
for (auto& item : m_items) {
|
||||
loadShortcuts(item);
|
||||
}
|
||||
}
|
||||
|
||||
void InputIndex::clone(InputIndex* root, bool actions) {
|
||||
if (!actions) {
|
||||
clone(const_cast<const InputIndex*>(root));
|
||||
} else {
|
||||
qDeleteAll(m_items);
|
||||
m_items.clear();
|
||||
for (auto& item : root->m_items) {
|
||||
InputItem* newItem = new InputItem(*item);
|
||||
m_items.append(newItem);
|
||||
itemAdded(newItem);
|
||||
}
|
||||
}
|
||||
rebuild();
|
||||
}
|
||||
|
||||
void InputIndex::clone(const InputIndex* root) {
|
||||
qDeleteAll(m_items);
|
||||
m_items.clear();
|
||||
for (auto& item : root->m_items) {
|
||||
InputItem* newItem = new InputItem(*item);
|
||||
m_items.append(newItem);
|
||||
itemAdded(newItem);
|
||||
}
|
||||
rebuild();
|
||||
}
|
||||
|
||||
void InputIndex::rebuild(const InputIndex* root) {
|
||||
if (!root) {
|
||||
root = this;
|
||||
}
|
||||
|
||||
m_names.clear();
|
||||
m_menus.clear();
|
||||
m_shortcuts.clear();
|
||||
m_buttons.clear();
|
||||
m_axes.clear();
|
||||
|
||||
for (auto& item : root->m_items) {
|
||||
InputItem* newItem = nullptr;
|
||||
for (auto &iter : m_items) {
|
||||
if (*iter == *item) {
|
||||
newItem = iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!newItem) {
|
||||
continue;
|
||||
}
|
||||
if (item->hasShortcut()) {
|
||||
newItem->setShortcut(item->shortcut());
|
||||
}
|
||||
if (item->hasButton()) {
|
||||
newItem->setButton(item->button());
|
||||
}
|
||||
if (item->hasAxis()) {
|
||||
newItem->setAxis(item->axis(), item->direction());
|
||||
}
|
||||
|
||||
itemAdded(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
InputItem* InputIndex::itemAt(const QString& name) {
|
||||
return m_names[name];
|
||||
}
|
||||
|
||||
const InputItem* InputIndex::itemAt(const QString& name) const {
|
||||
return m_names[name];
|
||||
}
|
||||
|
||||
InputItem* InputIndex::itemForMenu(const QMenu* menu) {
|
||||
InputItem* item = m_menus[menu];
|
||||
return item;
|
||||
}
|
||||
|
||||
const InputItem* InputIndex::itemForMenu(const QMenu* menu) const {
|
||||
const InputItem* item = m_menus[menu];
|
||||
return item;
|
||||
}
|
||||
|
||||
InputItem* InputIndex::itemForShortcut(int shortcut) {
|
||||
return m_shortcuts[shortcut];
|
||||
}
|
||||
|
||||
InputItem* InputIndex::itemForButton(int button) {
|
||||
return m_buttons[button];
|
||||
}
|
||||
|
||||
InputItem* InputIndex::itemForAxis(int axis, GamepadAxisEvent::Direction direction) {
|
||||
return m_axes[qMakePair(axis, direction)];
|
||||
}
|
||||
|
||||
bool InputIndex::loadShortcuts(InputItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return false;
|
||||
}
|
||||
loadGamepadShortcuts(item);
|
||||
QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION);
|
||||
if (!shortcut.isNull()) {
|
||||
if (shortcut.toString().endsWith("+")) {
|
||||
item->setShortcut(toModifierShortcut(shortcut.toString()));
|
||||
} else {
|
||||
item->setShortcut(QKeySequence(shortcut.toString())[0]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputIndex::loadGamepadShortcuts(InputItem* item) {
|
||||
if (item->name().isNull()) {
|
||||
return;
|
||||
}
|
||||
QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION);
|
||||
if (!button.isNull()) {
|
||||
item->setButton(button.toInt());
|
||||
}
|
||||
|
||||
QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION);
|
||||
int oldAxis = item->axis();
|
||||
if (oldAxis >= 0) {
|
||||
item->setAxis(-1, GamepadAxisEvent::NEUTRAL);
|
||||
}
|
||||
if (!axis.isNull()) {
|
||||
QString axisDesc = axis.toString();
|
||||
if (axisDesc.size() >= 2) {
|
||||
GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
|
||||
if (axisDesc[0] == '-') {
|
||||
direction = GamepadAxisEvent::NEGATIVE;
|
||||
}
|
||||
if (axisDesc[0] == '+') {
|
||||
direction = GamepadAxisEvent::POSITIVE;
|
||||
}
|
||||
bool ok;
|
||||
int axis = axisDesc.mid(1).toInt(&ok);
|
||||
if (ok) {
|
||||
item->setAxis(axis, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int InputIndex::toModifierShortcut(const QString& shortcut) {
|
||||
// Qt doesn't seem to work with raw modifier shortcuts!
|
||||
QStringList modifiers = shortcut.split('+');
|
||||
int value = 0;
|
||||
for (const auto& mod : modifiers) {
|
||||
if (mod == QLatin1String("Shift")) {
|
||||
value |= Qt::ShiftModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Ctrl")) {
|
||||
value |= Qt::ControlModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Alt")) {
|
||||
value |= Qt::AltModifier;
|
||||
continue;
|
||||
}
|
||||
if (mod == QLatin1String("Meta")) {
|
||||
value |= Qt::MetaModifier;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void InputIndex::itemAdded(InputItem* child) {
|
||||
const QMenu* menu = child->menu();
|
||||
if (menu) {
|
||||
m_menus[menu] = child;
|
||||
}
|
||||
m_names[child->name()] = child;
|
||||
|
||||
if (child->shortcut() > 0) {
|
||||
m_shortcuts[child->shortcut()] = child;
|
||||
}
|
||||
if (child->button() >= 0) {
|
||||
m_buttons[child->button()] = child;
|
||||
}
|
||||
if (child->direction() != GamepadAxisEvent::NEUTRAL) {
|
||||
m_axes[qMakePair(child->axis(), child->direction())] = child;
|
||||
}
|
||||
}
|
||||
|
||||
bool InputIndex::isModifierKey(int key) {
|
||||
switch (key) {
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_Control:
|
||||
case Qt::Key_Alt:
|
||||
case Qt::Key_Meta:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int InputIndex::toModifierKey(int key) {
|
||||
int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier);
|
||||
key ^= modifiers;
|
||||
switch (key) {
|
||||
case Qt::Key_Shift:
|
||||
modifiers |= Qt::ShiftModifier;
|
||||
break;
|
||||
case Qt::Key_Control:
|
||||
modifiers |= Qt::ControlModifier;
|
||||
break;
|
||||
case Qt::Key_Alt:
|
||||
modifiers |= Qt::AltModifier;
|
||||
break;
|
||||
case Qt::Key_Meta:
|
||||
modifiers |= Qt::MetaModifier;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
void InputIndex::saveConfig() {
|
||||
for (auto& item : m_items) {
|
||||
if (item->hasShortcut()) {
|
||||
m_config->setQtOption(item->name(), QKeySequence(item->shortcut()).toString(), KEY_SECTION);
|
||||
}
|
||||
if (item->hasButton()) {
|
||||
m_config->setQtOption(item->name(), item->button(), BUTTON_SECTION);
|
||||
}
|
||||
if (item->hasAxis()) {
|
||||
m_config->setQtOption(item->name(), QString("%1%2").arg(GamepadAxisEvent::POSITIVE ? '+' : '-').arg(item->axis()), AXIS_SECTION);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_INDEX
|
||||
#define QGBA_INPUT_INDEX
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "InputItem.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
class QMenu;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
|
||||
class InputIndex {
|
||||
private:
|
||||
constexpr static const char* const KEY_SECTION = "shortcutKey";
|
||||
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
|
||||
constexpr static const char* const AXIS_SECTION = "shortcutAxis";
|
||||
constexpr static const char* const HAT_SECTION = "shortcutHat";
|
||||
|
||||
public:
|
||||
void setConfigController(ConfigController* controller);
|
||||
|
||||
void clone(InputIndex* root, bool actions = false);
|
||||
void clone(const InputIndex* root);
|
||||
void rebuild(const InputIndex* root = nullptr);
|
||||
|
||||
const QList<InputItem*>& items() const { return m_items; }
|
||||
|
||||
template<typename... Args> InputItem* addItem(Args... params) {
|
||||
InputItem* newItem = new InputItem(params...);
|
||||
m_items.append(newItem);
|
||||
itemAdded(newItem);
|
||||
return newItem;
|
||||
}
|
||||
|
||||
InputItem* itemAt(const QString& name);
|
||||
const InputItem* itemAt(const QString& name) const;
|
||||
|
||||
InputItem* itemForMenu(const QMenu* menu);
|
||||
const InputItem* itemForMenu(const QMenu* menu) const;
|
||||
|
||||
InputItem* itemForShortcut(int shortcut);
|
||||
InputItem* itemForButton(int button);
|
||||
InputItem* itemForAxis(int axis, GamepadAxisEvent::Direction);
|
||||
|
||||
static int toModifierShortcut(const QString& shortcut);
|
||||
static bool isModifierKey(int key);
|
||||
static int toModifierKey(int key);
|
||||
|
||||
void saveConfig();
|
||||
|
||||
private:
|
||||
bool loadShortcuts(InputItem*);
|
||||
void loadGamepadShortcuts(InputItem*);
|
||||
|
||||
void itemAdded(InputItem*);
|
||||
|
||||
QList<InputItem*> m_items;
|
||||
|
||||
QMap<QString, InputItem*> m_names;
|
||||
QMap<const QMenu*, InputItem*> m_menus;
|
||||
QMap<int, InputItem*> m_shortcuts;
|
||||
QMap<int, InputItem*> m_buttons;
|
||||
QMap<QPair<int, GamepadAxisEvent::Direction>, InputItem*> m_axes;
|
||||
|
||||
ConfigController* m_config = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,108 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputItem.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
InputItem::InputItem()
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(QAction* action, const QString& name, QMenu* parent)
|
||||
: QObject(parent)
|
||||
, m_action(action)
|
||||
, m_shortcut(action->shortcut().isEmpty() ? -2 : action->shortcut()[0])
|
||||
, m_name(name)
|
||||
, m_menu(parent)
|
||||
{
|
||||
m_visibleName = action->text()
|
||||
.remove(QRegExp("&(?!&)"))
|
||||
.remove("...");
|
||||
}
|
||||
|
||||
InputItem::InputItem(InputItem::Functions functions, const QString& visibleName, const QString& name, QMenu* parent)
|
||||
: QObject(parent)
|
||||
, m_functions(functions)
|
||||
, m_name(name)
|
||||
, m_visibleName(visibleName)
|
||||
, m_menu(parent)
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(const QString& visibleName, const QString& name, QMenu* parent)
|
||||
: QObject(parent)
|
||||
, m_name(name)
|
||||
, m_visibleName(visibleName)
|
||||
, m_menu(parent)
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(const InputItem& other)
|
||||
: QObject(other.m_menu)
|
||||
, m_name(other.m_name)
|
||||
, m_visibleName(other.m_visibleName)
|
||||
, m_shortcut(other.m_shortcut)
|
||||
, m_button(other.m_button)
|
||||
, m_axis(other.m_axis)
|
||||
, m_direction(other.m_direction)
|
||||
, m_menu(other.m_menu)
|
||||
{
|
||||
}
|
||||
|
||||
InputItem::InputItem(InputItem& other)
|
||||
: QObject(other.m_menu)
|
||||
, m_action(other.m_action)
|
||||
, m_functions(other.m_functions)
|
||||
, m_name(other.m_name)
|
||||
, m_visibleName(other.m_visibleName)
|
||||
, m_shortcut(other.m_shortcut)
|
||||
, m_button(other.m_button)
|
||||
, m_axis(other.m_axis)
|
||||
, m_direction(other.m_direction)
|
||||
, m_menu(other.m_menu)
|
||||
{
|
||||
}
|
||||
|
||||
void InputItem::setShortcut(int shortcut) {
|
||||
m_shortcut = shortcut;
|
||||
if (m_action) {
|
||||
m_action->setShortcut(QKeySequence(shortcut));
|
||||
}
|
||||
emit shortcutBound(this, shortcut);
|
||||
}
|
||||
|
||||
void InputItem::clearShortcut() {
|
||||
setShortcut(0);
|
||||
}
|
||||
|
||||
void InputItem::setButton(int button) {
|
||||
m_button = button;
|
||||
emit buttonBound(this, button);
|
||||
}
|
||||
|
||||
void InputItem::clearButton() {
|
||||
setButton(-1);
|
||||
}
|
||||
|
||||
void InputItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
|
||||
m_axis = axis;
|
||||
m_direction = direction;
|
||||
emit axisBound(this, axis, direction);
|
||||
}
|
||||
|
||||
void InputItem::trigger(bool active) {
|
||||
if (active) {
|
||||
if (m_functions.first) {
|
||||
m_functions.first();
|
||||
}
|
||||
} else {
|
||||
if (m_functions.second) {
|
||||
m_functions.second();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* Copyright (c) 2013-2017 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef QGBA_INPUT_ITEM
|
||||
#define QGBA_INPUT_ITEM
|
||||
|
||||
#include "GamepadAxisEvent.h"
|
||||
#include "GamepadHatEvent.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QAction>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class InputItem : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef QPair<std::function<void ()>, std::function<void ()>> Functions;
|
||||
|
||||
InputItem(QAction* action, const QString& name, QMenu* parent = nullptr);
|
||||
InputItem(Functions functions, const QString& visibleName, const QString& name,
|
||||
QMenu* parent = nullptr);
|
||||
InputItem(const QString& visibleName, const QString& name, QMenu* parent = nullptr);
|
||||
|
||||
InputItem();
|
||||
InputItem(InputItem&);
|
||||
InputItem(const InputItem&);
|
||||
|
||||
QAction* action() { return m_action; }
|
||||
const QAction* action() const { return m_action; }
|
||||
Functions functions() const { return m_functions; }
|
||||
|
||||
QMenu* menu() { return m_menu; }
|
||||
const QMenu* menu() const { return m_menu; }
|
||||
|
||||
const QString& visibleName() const { return m_visibleName; }
|
||||
const QString& name() const { return m_name; }
|
||||
|
||||
int shortcut() const { return m_shortcut; }
|
||||
void setShortcut(int sequence);
|
||||
void clearShortcut();
|
||||
bool hasShortcut() { return m_shortcut > -2; }
|
||||
|
||||
int button() const { return m_button; }
|
||||
void setButton(int button);
|
||||
void clearButton();
|
||||
bool hasButton() { return m_button > -2; }
|
||||
|
||||
int axis() const { return m_axis; }
|
||||
GamepadAxisEvent::Direction direction() const { return m_direction; }
|
||||
void setAxis(int axis, GamepadAxisEvent::Direction direction);
|
||||
bool hasAxis() { return m_axis > -2; }
|
||||
|
||||
bool operator==(const InputItem& other) const {
|
||||
return m_name == other.m_name;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void trigger(bool = true);
|
||||
|
||||
signals:
|
||||
void shortcutBound(InputItem*, int shortcut);
|
||||
void buttonBound(InputItem*, int button);
|
||||
void axisBound(InputItem*, int axis, GamepadAxisEvent::Direction);
|
||||
void childAdded(InputItem* parent, InputItem* child);
|
||||
|
||||
private:
|
||||
QAction* m_action = nullptr;
|
||||
Functions m_functions;
|
||||
|
||||
QMenu* m_menu = nullptr;
|
||||
QString m_name;
|
||||
QString m_visibleName;
|
||||
|
||||
int m_shortcut = -2;
|
||||
int m_button = -2;
|
||||
int m_axis = -2;
|
||||
GamepadAxisEvent::Direction m_direction = GamepadAxisEvent::NEUTRAL;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(QGBA::InputItem)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,184 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "InputModel.h"
|
||||
|
||||
#include "GamepadButtonEvent.h"
|
||||
#include "InputIndex.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
QString InputModel::InputModelItem::visibleName() const {
|
||||
if (item) {
|
||||
return item->visibleName();
|
||||
}
|
||||
if (menu) {
|
||||
return menu->title()
|
||||
.remove(QRegExp("&(?!&)"))
|
||||
.remove("...");
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
InputModel::InputModel(const InputIndex& index, QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
clone(index);
|
||||
}
|
||||
|
||||
InputModel::InputModel(QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void InputModel::clone(const InputIndex& index) {
|
||||
emit beginResetModel();
|
||||
m_index.clone(&index);
|
||||
m_menus.clear();
|
||||
m_topLevelMenus.clear();
|
||||
QList<const QMenu*> menus;
|
||||
for (auto& item : m_index.items()) {
|
||||
const QMenu* menu = item->menu();
|
||||
if (!menus.contains(menu)) {
|
||||
menus.append(menu);
|
||||
m_menus.insert(menu);
|
||||
}
|
||||
}
|
||||
for (auto& menu : menus) {
|
||||
if (m_menus.contains(menu->parent())) {
|
||||
m_tree[menu->parent()].append(menu);
|
||||
} else {
|
||||
m_topLevelMenus.append(menu);
|
||||
}
|
||||
}
|
||||
for (auto& item : m_index.items()) {
|
||||
const QMenu* menu = item->menu();
|
||||
m_tree[menu].append(item);
|
||||
}
|
||||
emit endResetModel();
|
||||
}
|
||||
|
||||
QVariant InputModel::data(const QModelIndex& index, int role) const {
|
||||
if (role != Qt::DisplayRole || !index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
int row = index.row();
|
||||
const InputModelItem* item = static_cast<const InputModelItem*>(index.internalPointer());
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return item->visibleName();
|
||||
case 1:
|
||||
if (item->item && item->item->shortcut() > 0) {
|
||||
return QKeySequence(item->item->shortcut()).toString(QKeySequence::NativeText);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!item->item) {
|
||||
break;
|
||||
}
|
||||
if (item->item->button() >= 0) {
|
||||
return item->item->button();
|
||||
}
|
||||
if (item->item->axis() >= 0) {
|
||||
char d = '\0';
|
||||
if (item->item->direction() == GamepadAxisEvent::POSITIVE) {
|
||||
d = '+';
|
||||
}
|
||||
if (item->item->direction() == GamepadAxisEvent::NEGATIVE) {
|
||||
d = '-';
|
||||
}
|
||||
return QString("%1%2").arg(d).arg(item->item->axis());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant InputModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QAbstractItemModel::headerData(section, orientation, role);
|
||||
}
|
||||
if (orientation == Qt::Horizontal) {
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("Action");
|
||||
case 1:
|
||||
return tr("Keyboard");
|
||||
case 2:
|
||||
return tr("Gamepad");
|
||||
}
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
InputModelItem* p = static_cast<InputModelItem*>(parent.internalPointer());
|
||||
return createIndex(row, column, const_cast<InputModelItem*>(&m_tree[p->obj][row]));
|
||||
}
|
||||
return createIndex(row, column, const_cast<InputModelItem*>(&m_topLevelMenus[row]));
|
||||
}
|
||||
|
||||
QModelIndex InputModel::parent(const QModelIndex& index) const {
|
||||
if (!index.isValid() || !index.internalPointer()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
const QObject* obj = static_cast<const InputModelItem*>(index.internalPointer())->obj;
|
||||
if (m_menus.contains(obj->parent())) {
|
||||
return this->index(obj->parent());
|
||||
} else {
|
||||
return QModelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex InputModel::index(const QObject* item, int column) const {
|
||||
if (!item) {
|
||||
return QModelIndex();
|
||||
}
|
||||
const QObject* parent = item->parent();
|
||||
if (parent && m_tree.contains(parent)) {
|
||||
int index = m_tree[parent].indexOf(item);
|
||||
return createIndex(index, column, const_cast<InputModelItem*>(&m_tree[parent][index]));
|
||||
}
|
||||
if (m_topLevelMenus.contains(item)) {
|
||||
int index = m_topLevelMenus.indexOf(item);
|
||||
return createIndex(index, column, const_cast<InputModelItem*>(&m_topLevelMenus[index]));
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int InputModel::columnCount(const QModelIndex& index) const {
|
||||
return 3;
|
||||
}
|
||||
|
||||
int InputModel::rowCount(const QModelIndex& index) const {
|
||||
if (!index.isValid() || !index.internalPointer()) {
|
||||
return m_topLevelMenus.count();
|
||||
}
|
||||
const QObject* obj = static_cast<const InputModelItem*>(index.internalPointer())->obj;
|
||||
if (!m_tree.contains(obj)) {
|
||||
return 0;
|
||||
}
|
||||
return m_tree[obj].count();
|
||||
}
|
||||
|
||||
InputItem* InputModel::itemAt(const QModelIndex& index) {
|
||||
if (!index.isValid() || !index.internalPointer()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<InputModelItem*>(index.internalPointer())->item;
|
||||
}
|
||||
|
||||
const InputItem* InputModel::itemAt(const QModelIndex& index) const {
|
||||
if (!index.isValid() || !index.internalPointer()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const InputModelItem*>(index.internalPointer())->item;
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue