InputManager: Fix chord bindings when activating in reverse

This commit is contained in:
Connor McLaughlin 2022-06-21 19:31:34 +10:00 committed by refractionpcsx2
parent b20e5a1e01
commit 14181ec70d
5 changed files with 97 additions and 95 deletions

View File

@ -952,7 +952,7 @@ SysMtgsThread& GetMTGS()
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
BEGIN_HOTKEY_LIST(g_host_hotkeys) BEGIN_HOTKEY_LIST(g_host_hotkeys)
DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](bool pressed) { DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
// run it on the host thread, that way we get the confirm prompt (if enabled) // run it on the host thread, that way we get the confirm prompt (if enabled)
@ -960,16 +960,16 @@ DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](bool press
Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, true)); Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, true));
} }
}) })
DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](bool pressed) { DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](s32 pressed) {
if (!pressed) if (!pressed)
g_emu_thread->setVMPaused(VMManager::GetState() != VMState::Paused); g_emu_thread->setVMPaused(VMManager::GetState() != VMState::Paused);
}) })
DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](bool pressed) { DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](s32 pressed) {
if (!pressed) if (!pressed)
g_emu_thread->toggleFullscreen(); g_emu_thread->toggleFullscreen();
}) })
// Input Recording Hot Keys // Input Recording Hot Keys
DEFINE_HOTKEY("InputRecToggleMode", "Input Recording", "Toggle Recording Mode", [](bool pressed) { DEFINE_HOTKEY("InputRecToggleMode", "Input Recording", "Toggle Recording Mode", [](s32 pressed) {
if (!pressed) // ?? - not pressed so it is on key up? if (!pressed) // ?? - not pressed so it is on key up?
{ {
g_InputRecordingControls.RecordModeToggle(); g_InputRecordingControls.RecordModeToggle();

View File

@ -622,51 +622,20 @@ bool InputManager::IsAxisHandler(const InputEventHandler& handler)
bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key) bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key)
{ {
if (PreprocessEvent(key, value, generic_key)) if (DoEventHook(key, value))
return true; return true;
// If imgui ate the event, don't fire our handlers.
const bool skip_button_handlers = PreprocessEvent(key, value, generic_key);
// find all the bindings associated with this key // find all the bindings associated with this key
const InputBindingKey masked_key = key.MaskDirection(); const InputBindingKey masked_key = key.MaskDirection();
const auto range = s_binding_map.equal_range(masked_key); const auto range = s_binding_map.equal_range(masked_key);
if (range.first == s_binding_map.end()) if (range.first == s_binding_map.end())
return false; return false;
// Workaround for modifier keys. Basically, if we bind say, F1 and Shift+F1, and press shift
// and then F1, we'll fire bindings for both F1 and Shift+F1, when we really only want to fire
// the binding for Shift+F1. So, let's search through the binding list, and see if there's a
// "longer" binding (more keys), and if so, only activate that and not the shorter binding(s).
const InputBinding* longest_hotkey_binding = nullptr;
for (auto it = range.first; it != range.second; ++it)
{
InputBinding* binding = it->second.get();
if (IsAxisHandler(binding->handler))
continue;
// find the key which matches us
for (u32 i = 0; i < binding->num_keys; i++)
{
if (binding->keys[i].MaskDirection() != masked_key)
continue;
const u8 bit = static_cast<u8>(1) << i;
const bool negative = binding->keys[i].negative;
const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f));
const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit));
const bool prev_full_state = (binding->current_mask == binding->full_mask);
const bool new_full_state = (new_mask == binding->full_mask);
// If we're activating this chord, block activation of other bindings with fewer keys.
if (prev_full_state || new_full_state)
{
if (!longest_hotkey_binding || longest_hotkey_binding->num_keys < binding->num_keys)
longest_hotkey_binding = binding;
}
break;
}
}
// Now we can actually fire/activate bindings. // Now we can actually fire/activate bindings.
u32 min_num_keys = 0;
for (auto it = range.first; it != range.second; ++it) for (auto it = range.first; it != range.second; ++it)
{ {
InputBinding* binding = it->second.get(); InputBinding* binding = it->second.get();
@ -681,20 +650,6 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi
const bool negative = binding->keys[i].negative; const bool negative = binding->keys[i].negative;
const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f)); const bool new_state = (negative ? (value < 0.0f) : (value > 0.0f));
// Don't register the key press when we're part of a longer chord. That way,
// the state won't change, and it won't get the released event either.
if (longest_hotkey_binding && new_state && !IsAxisHandler(binding->handler) &&
binding->num_keys != longest_hotkey_binding->num_keys)
{
continue;
}
// update state based on whether the whole chord was activated
const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit));
const bool prev_full_state = (binding->current_mask == binding->full_mask);
const bool new_full_state = (new_mask == binding->full_mask);
binding->current_mask = new_mask;
// invert if we're negative, since the handler expects 0..1 // invert if we're negative, since the handler expects 0..1
const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value : 0.0f); const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value : 0.0f);
@ -704,13 +659,60 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi
// and 0 on release (when the full state changes). // and 0 on release (when the full state changes).
if (IsAxisHandler(binding->handler)) if (IsAxisHandler(binding->handler))
{ {
if (prev_full_state != new_full_state || value_to_pass >= 0.0f) if (value_to_pass >= 0.0f)
std::get<InputAxisEventHandler>(binding->handler)(value_to_pass); std::get<InputAxisEventHandler>(binding->handler)(value_to_pass);
} }
else else if (binding->num_keys >= min_num_keys)
{ {
if (prev_full_state != new_full_state) // update state based on whether the whole chord was activated
std::get<InputButtonEventHandler>(binding->handler)(value_to_pass > 0.0f); const u8 new_mask = (new_state ? (binding->current_mask | bit) : (binding->current_mask & ~bit));
const bool prev_full_state = (binding->current_mask == binding->full_mask);
const bool new_full_state = (new_mask == binding->full_mask);
binding->current_mask = new_mask;
// Workaround for multi-key bindings that share the same keys.
if (binding->num_keys > 1 && new_full_state && prev_full_state != new_full_state &&
range.first != range.second)
{
// Because the binding map isn't ordered, we could iterate in the order of Shift+F1 and then
// F1, which would mean that F1 wouldn't get cancelled and still activate. So, to handle this
// case, we skip activating any future bindings with a fewer number of keys.
min_num_keys = std::max<u32>(min_num_keys, binding->num_keys);
// Basically, if we bind say, F1 and Shift+F1, and press shift and then F1, we'll fire bindings
// for both F1 and Shift+F1, when we really only want to fire the binding for Shift+F1. So,
// when we activate a multi-key chord (key press), we go through the binding map for all the
// other keys in the chord, and cancel them if they have a shorter chord. If they're longer,
// they could still activate and take precedence over us, so we leave them alone.
for (u32 i = 0; i < binding->num_keys; i++)
{
const auto range = s_binding_map.equal_range(binding->keys[i].MaskDirection());
for (auto it = range.first; it != range.second; ++it)
{
InputBinding* other_binding = it->second.get();
if (other_binding == binding || IsAxisHandler(other_binding->handler) ||
other_binding->num_keys >= binding->num_keys)
{
continue;
}
// We only need to cancel the binding if it was fully active before. Which in the above
// case of Shift+F1 / F1, it will be.
if (other_binding->current_mask == other_binding->full_mask)
std::get<InputButtonEventHandler>(other_binding->handler)(-1);
// Zero out the current bits so that we don't release this binding, if the other part
// of the chord releases first.
other_binding->current_mask = 0;
}
}
}
if (prev_full_state != new_full_state && binding->num_keys >= min_num_keys)
{
const s32 pressed = skip_button_handlers ? -1 : static_cast<s32>(value_to_pass > 0.0f);
std::get<InputButtonEventHandler>(binding->handler)(pressed);
}
} }
// bail out, since we shouldn't have the same key twice in the chord // bail out, since we shouldn't have the same key twice in the chord
@ -723,9 +725,6 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBi
bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key) bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key)
{ {
if (DoEventHook(key, value))
return true;
// does imgui want the event? // does imgui want the event?
if (key.source_type == InputSourceType::Keyboard) if (key.source_type == InputSourceType::Keyboard)
{ {
@ -739,7 +738,7 @@ bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInpu
} }
else if (generic_key != GenericInputBinding::Unknown) else if (generic_key != GenericInputBinding::Unknown)
{ {
if (ImGuiManager::ProcessGenericInputEvent(generic_key, value)) if (ImGuiManager::ProcessGenericInputEvent(generic_key, value) && value != 0.0f)
return true; return true;
} }

View File

@ -90,7 +90,7 @@ struct InputBindingKeyHash
}; };
/// Callback type for a binary event. Usually used for hotkeys. /// Callback type for a binary event. Usually used for hotkeys.
using InputButtonEventHandler = std::function<void(bool value)>; using InputButtonEventHandler = std::function<void(s32 value)>;
/// Callback types for a normalized event. Usually used for pads. /// Callback types for a normalized event. Usually used for pads.
using InputAxisEventHandler = std::function<void(float value)>; using InputAxisEventHandler = std::function<void(float value)>;
@ -110,12 +110,15 @@ struct InputInterceptHook
}; };
/// Hotkeys are actions (e.g. toggle frame limit) which can be bound to keys or chords. /// Hotkeys are actions (e.g. toggle frame limit) which can be bound to keys or chords.
/// The handler is called with an integer representing the key state, where 0 means that
/// one or more keys were released, 1 means all the keys were pressed, and -1 means that
/// the hotkey was cancelled due to a chord with more keys being activated.
struct HotkeyInfo struct HotkeyInfo
{ {
const char* name; const char* name;
const char* category; const char* category;
const char* display_name; const char* display_name;
void (*handler)(bool pressed); void (*handler)(s32 pressed);
}; };
#define DECLARE_HOTKEY_LIST(name) extern const HotkeyInfo name[] #define DECLARE_HOTKEY_LIST(name) extern const HotkeyInfo name[]
#define BEGIN_HOTKEY_LIST(name) const HotkeyInfo name[] = { #define BEGIN_HOTKEY_LIST(name) const HotkeyInfo name[] = {

View File

@ -1623,7 +1623,7 @@ static void HotkeyAdjustZoom(double delta)
} }
BEGIN_HOTKEY_LIST(g_gs_hotkeys) BEGIN_HOTKEY_LIST(g_gs_hotkeys)
{"Screenshot", "Graphics", "Save Screenshot", [](bool pressed) { {"Screenshot", "Graphics", "Save Screenshot", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
GetMTGS().RunOnGSThread([]() { GetMTGS().RunOnGSThread([]() {
@ -1631,7 +1631,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
}); });
} }
}}, }},
{"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](bool pressed) { {"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
GetMTGS().RunOnGSThread([]() { GetMTGS().RunOnGSThread([]() {
@ -1639,27 +1639,27 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
}); });
} }
}}, }},
{"GSDumpMultiFrame", "Graphics", "Save Multi Frame GS Dump", [](bool pressed) { {"GSDumpMultiFrame", "Graphics", "Save Multi Frame GS Dump", [](s32 pressed) {
GetMTGS().RunOnGSThread([pressed]() { GetMTGS().RunOnGSThread([pressed]() {
if (pressed) if (pressed > 0)
GSQueueSnapshot(std::string(), std::numeric_limits<u32>::max()); GSQueueSnapshot(std::string(), std::numeric_limits<u32>::max());
else else
GSStopGSDump(); GSStopGSDump();
}); });
}}, }},
{"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](bool pressed) { {"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](s32 pressed) {
if (!pressed) if (!pressed)
GetMTGS().ToggleSoftwareRendering(); GetMTGS().ToggleSoftwareRendering();
}}, }},
{"IncreaseUpscaleMultiplier", "Graphics", "Increase Upscale Multiplier", [](bool pressed) { {"IncreaseUpscaleMultiplier", "Graphics", "Increase Upscale Multiplier", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustUpscaleMultiplier(1); HotkeyAdjustUpscaleMultiplier(1);
}}, }},
{"DecreaseUpscaleMultiplier", "Graphics", "Decrease Upscale Multiplier", [](bool pressed) { {"DecreaseUpscaleMultiplier", "Graphics", "Decrease Upscale Multiplier", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustUpscaleMultiplier(-1); HotkeyAdjustUpscaleMultiplier(-1);
}}, }},
{"CycleAspectRatio", "Graphics", "Cycle Aspect Ratio", [](bool pressed) { {"CycleAspectRatio", "Graphics", "Cycle Aspect Ratio", [](s32 pressed) {
if (pressed) if (pressed)
return; return;
@ -1667,7 +1667,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
EmuConfig.CurrentAspectRatio = static_cast<AspectRatioType>((static_cast<int>(EmuConfig.CurrentAspectRatio) + 1) % static_cast<int>(AspectRatioType::MaxCount)); EmuConfig.CurrentAspectRatio = static_cast<AspectRatioType>((static_cast<int>(EmuConfig.CurrentAspectRatio) + 1) % static_cast<int>(AspectRatioType::MaxCount));
Host::AddKeyedFormattedOSDMessage("CycleAspectRatio", 10.0f, "Aspect ratio set to '%s'.", Pcsx2Config::GSOptions::AspectRatioNames[static_cast<int>(EmuConfig.CurrentAspectRatio)]); Host::AddKeyedFormattedOSDMessage("CycleAspectRatio", 10.0f, "Aspect ratio set to '%s'.", Pcsx2Config::GSOptions::AspectRatioNames[static_cast<int>(EmuConfig.CurrentAspectRatio)]);
}}, }},
{"CycleMipmapMode", "Graphics", "Cycle Hardware Mipmapping", [](bool pressed) { {"CycleMipmapMode", "Graphics", "Cycle Hardware Mipmapping", [](s32 pressed) {
if (pressed) if (pressed)
return; return;
@ -1684,7 +1684,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
g_gs_renderer->PurgePool(); g_gs_renderer->PurgePool();
}); });
}}, }},
{"CycleInterlaceMode", "Graphics", "Cycle Deinterlace Mode", [](bool pressed) { {"CycleInterlaceMode", "Graphics", "Cycle Deinterlace Mode", [](s32 pressed) {
if (pressed) if (pressed)
return; return;
@ -1705,15 +1705,15 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
GetMTGS().RunOnGSThread([new_mode]() { GSConfig.InterlaceMode = new_mode; }); GetMTGS().RunOnGSThread([new_mode]() { GSConfig.InterlaceMode = new_mode; });
}}, }},
{"ZoomIn", "Graphics", "Zoom In", [](bool pressed) { {"ZoomIn", "Graphics", "Zoom In", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustZoom(1.0); HotkeyAdjustZoom(1.0);
}}, }},
{"ZoomOut", "Graphics", "Zoom Out", [](bool pressed) { {"ZoomOut", "Graphics", "Zoom Out", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustZoom(-1.0); HotkeyAdjustZoom(-1.0);
}}, }},
{"ToggleTextureDumping", "Graphics", "Toggle Texture Dumping", [](bool pressed) { {"ToggleTextureDumping", "Graphics", "Toggle Texture Dumping", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
EmuConfig.GS.DumpReplaceableTextures = !EmuConfig.GS.DumpReplaceableTextures; EmuConfig.GS.DumpReplaceableTextures = !EmuConfig.GS.DumpReplaceableTextures;
@ -1721,7 +1721,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
GetMTGS().ApplySettings(); GetMTGS().ApplySettings();
} }
}}, }},
{"ToggleTextureReplacements", "Graphics", "Toggle Texture Replacements", [](bool pressed) { {"ToggleTextureReplacements", "Graphics", "Toggle Texture Replacements", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
EmuConfig.GS.LoadTextureReplacements = !EmuConfig.GS.LoadTextureReplacements; EmuConfig.GS.LoadTextureReplacements = !EmuConfig.GS.LoadTextureReplacements;
@ -1729,7 +1729,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys)
GetMTGS().ApplySettings(); GetMTGS().ApplySettings();
} }
}}, }},
{"ReloadTextureReplacements", "Graphics", "Reload Texture Replacements", [](bool pressed) { {"ReloadTextureReplacements", "Graphics", "Reload Texture Replacements", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
if (!EmuConfig.GS.LoadTextureReplacements) if (!EmuConfig.GS.LoadTextureReplacements)

View File

@ -1713,7 +1713,7 @@ static void HotkeySaveStateSlot(s32 slot)
} }
BEGIN_HOTKEY_LIST(g_vm_manager_hotkeys) BEGIN_HOTKEY_LIST(g_vm_manager_hotkeys)
DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](bool pressed) { DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Unlimited) ? VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Unlimited) ?
@ -1721,7 +1721,7 @@ DEFINE_HOTKEY("ToggleFrameLimit", "System", "Toggle Frame Limit", [](bool presse
LimiterModeType::Nominal); LimiterModeType::Nominal);
} }
}) })
DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](bool pressed) { DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Turbo) ? VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Turbo) ?
@ -1729,21 +1729,21 @@ DEFINE_HOTKEY("ToggleTurbo", "System", "Toggle Turbo", [](bool pressed) {
LimiterModeType::Nominal); LimiterModeType::Nominal);
} }
}) })
DEFINE_HOTKEY("HoldTurbo", "System", "Turbo (Hold)", [](bool pressed) { DEFINE_HOTKEY("HoldTurbo", "System", "Turbo (Hold)", [](s32 pressed) {
if (pressed && !s_limiter_mode_prior_to_hold_interaction.has_value()) if (pressed > 0 && !s_limiter_mode_prior_to_hold_interaction.has_value())
{ {
s_limiter_mode_prior_to_hold_interaction = VMManager::GetLimiterMode(); s_limiter_mode_prior_to_hold_interaction = VMManager::GetLimiterMode();
VMManager::SetLimiterMode((s_limiter_mode_prior_to_hold_interaction.value() != LimiterModeType::Turbo) ? VMManager::SetLimiterMode((s_limiter_mode_prior_to_hold_interaction.value() != LimiterModeType::Turbo) ?
LimiterModeType::Turbo : LimiterModeType::Turbo :
LimiterModeType::Nominal); LimiterModeType::Nominal);
} }
else if (!pressed && s_limiter_mode_prior_to_hold_interaction.has_value()) else if (pressed >= 0 && s_limiter_mode_prior_to_hold_interaction.has_value())
{ {
VMManager::SetLimiterMode(s_limiter_mode_prior_to_hold_interaction.value()); VMManager::SetLimiterMode(s_limiter_mode_prior_to_hold_interaction.value());
s_limiter_mode_prior_to_hold_interaction.reset(); s_limiter_mode_prior_to_hold_interaction.reset();
} }
}) })
DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](bool pressed) { DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](s32 pressed) {
if (!pressed) if (!pressed)
{ {
VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Slomo) ? VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Slomo) ?
@ -1751,42 +1751,42 @@ DEFINE_HOTKEY("ToggleSlowMotion", "System", "Toggle Slow Motion", [](bool presse
LimiterModeType::Nominal); LimiterModeType::Nominal);
} }
}) })
DEFINE_HOTKEY("IncreaseSpeed", "System", "Increase Target Speed", [](bool pressed) { DEFINE_HOTKEY("IncreaseSpeed", "System", "Increase Target Speed", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustTargetSpeed(0.1); HotkeyAdjustTargetSpeed(0.1);
}) })
DEFINE_HOTKEY("DecreaseSpeed", "System", "Decrease Target Speed", [](bool pressed) { DEFINE_HOTKEY("DecreaseSpeed", "System", "Decrease Target Speed", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyAdjustTargetSpeed(-0.1); HotkeyAdjustTargetSpeed(-0.1);
}) })
DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](bool pressed) { DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM()) if (!pressed && VMManager::HasValidVM())
VMManager::Reset(); VMManager::Reset();
}) })
DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](bool pressed) { DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](s32 pressed) {
if (!pressed) if (!pressed)
VMManager::FrameAdvance(1); VMManager::FrameAdvance(1);
}) })
DEFINE_HOTKEY("PreviousSaveStateSlot", "Save States", "Select Previous Save Slot", [](bool pressed) { DEFINE_HOTKEY("PreviousSaveStateSlot", "Save States", "Select Previous Save Slot", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyCycleSaveSlot(-1); HotkeyCycleSaveSlot(-1);
}) })
DEFINE_HOTKEY("NextSaveStateSlot", "Save States", "Select Next Save Slot", [](bool pressed) { DEFINE_HOTKEY("NextSaveStateSlot", "Save States", "Select Next Save Slot", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyCycleSaveSlot(1); HotkeyCycleSaveSlot(1);
}) })
DEFINE_HOTKEY("SaveStateToSlot", "Save States", "Save State To Selected Slot", [](bool pressed) { DEFINE_HOTKEY("SaveStateToSlot", "Save States", "Save State To Selected Slot", [](s32 pressed) {
if (!pressed) if (!pressed)
VMManager::SaveStateToSlot(s_current_save_slot); VMManager::SaveStateToSlot(s_current_save_slot);
}) })
DEFINE_HOTKEY("LoadStateFromSlot", "Save States", "Load State From Selected Slot", [](bool pressed) { DEFINE_HOTKEY("LoadStateFromSlot", "Save States", "Load State From Selected Slot", [](s32 pressed) {
if (!pressed) if (!pressed)
HotkeyLoadStateSlot(s_current_save_slot); HotkeyLoadStateSlot(s_current_save_slot);
}) })
#define DEFINE_HOTKEY_SAVESTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("SaveStateToSlot" #slotnum, \ #define DEFINE_HOTKEY_SAVESTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("SaveStateToSlot" #slotnum, \
"Save States", "Save State To Slot " #slotnumstr, [](bool pressed) { if (!pressed) HotkeySaveStateSlot(slotnum); }) "Save States", "Save State To Slot " #slotnumstr, [](s32 pressed) { if (!pressed) HotkeySaveStateSlot(slotnum); })
DEFINE_HOTKEY_SAVESTATE_X(1, 01) DEFINE_HOTKEY_SAVESTATE_X(1, 01)
DEFINE_HOTKEY_SAVESTATE_X(2, 02) DEFINE_HOTKEY_SAVESTATE_X(2, 02)
DEFINE_HOTKEY_SAVESTATE_X(3, 03) DEFINE_HOTKEY_SAVESTATE_X(3, 03)
@ -1798,7 +1798,7 @@ DEFINE_HOTKEY_SAVESTATE_X(8, 08)
DEFINE_HOTKEY_SAVESTATE_X(9, 09) DEFINE_HOTKEY_SAVESTATE_X(9, 09)
DEFINE_HOTKEY_SAVESTATE_X(10, 10) DEFINE_HOTKEY_SAVESTATE_X(10, 10)
#define DEFINE_HOTKEY_LOADSTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("LoadStateFromSlot" #slotnum, \ #define DEFINE_HOTKEY_LOADSTATE_X(slotnum, slotnumstr) DEFINE_HOTKEY("LoadStateFromSlot" #slotnum, \
"Save States", "Load State From Slot " #slotnumstr, [](bool pressed) { \ "Save States", "Load State From Slot " #slotnumstr, [](s32 pressed) { \
if (!pressed) \ if (!pressed) \
HotkeyLoadStateSlot(slotnum); \ HotkeyLoadStateSlot(slotnum); \
}) })