[UserInput] Only process shortcut commands once

This modifies the UserInputEvent class to fire a vector of events at
once, rather than individual down/up events for each UserInput. This
simplifies handling of the global event filter and prevents the firing
of spurious events.

This also fixes a bug when pressing "Ctrl+1" would trigger the command
for both the command assigned to "Ctrl+1" and to "1". Now, only the
"Ctrl+1" command will fire.
This commit is contained in:
Fabrice de Gans 2024-05-04 13:06:20 -07:00 committed by Fabrice de Gans
parent d32be9ddbe
commit 902c6c8e4b
8 changed files with 201 additions and 123 deletions

View File

@ -1177,8 +1177,7 @@ void GameArea::OnIdle(wxIdleEvent& event)
wxWindow* w = panel->GetWindow(); wxWindow* w = panel->GetWindow();
// set up event handlers // set up event handlers
w->Bind(VBAM_EVT_USER_INPUT_DOWN, &GameArea::OnUserInputDown, this); w->Bind(VBAM_EVT_USER_INPUT, &GameArea::OnUserInput, this);
w->Bind(VBAM_EVT_USER_INPUT_UP, &GameArea::OnUserInputUp, this);
w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this); w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this);
w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this); w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this);
@ -1330,26 +1329,33 @@ static Display* GetX11Display() {
} }
#endif // __WXGTK__ #endif // __WXGTK__
void GameArea::OnUserInputDown(widgets::UserInputEvent& event) { void GameArea::OnUserInput(widgets::UserInputEvent& event) {
if (wxGetApp().emulated_gamepad()->OnInputPressed(event.input())) { bool emulated_key_pressed = false;
wxWakeUpIdle(); for (const auto& event_data : event.data()) {
} if (event_data.pressed) {
} if (wxGetApp().emulated_gamepad()->OnInputPressed(event_data.input)) {
emulated_key_pressed = true;
void GameArea::OnUserInputUp(widgets::UserInputEvent& event) { }
if (wxGetApp().emulated_gamepad()->OnInputReleased(event.input())) { } else {
wxWakeUpIdle(); if (wxGetApp().emulated_gamepad()->OnInputReleased(event_data.input)) {
emulated_key_pressed = true;
}
}
} }
// tell Linux to turn off the screensaver/screen-blank if joystick button was pressed if (emulated_key_pressed) {
// this shouldn't be necessary of course wxWakeUpIdle();
#if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS) #if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS)
if (event.input().is_joystick() && !wxGetApp().UsingWayland()) { // Tell X11 to turn off the screensaver/screen-blank if a button was
auto display = GetX11Display(); // was pressed. This shouldn't be necessary.
XResetScreenSaver(display); if (!wxGetApp().UsingWayland()) {
XFlush(display); auto display = GetX11Display();
} XResetScreenSaver(display);
XFlush(display);
}
#endif #endif
}
} }
// these three are forwarded to the DrawingPanel instance // these three are forwarded to the DrawingPanel instance

View File

@ -3,7 +3,6 @@
#include <cassert> #include <cassert>
#include <map> #include <map>
#include <SDL_events.h>
#include <wx/timer.h> #include <wx/timer.h>
#include <wx/toplevel.h> #include <wx/toplevel.h>
@ -86,9 +85,10 @@ public:
bool is_game_controller() const { return !!game_controller_; } bool is_game_controller() const { return !!game_controller_; }
// Processes the corresponding events. // Processes the corresponding events.
std::vector<UserInputEvent> ProcessAxisEvent(const uint8_t index, const JoyAxisStatus status); std::vector<UserInputEvent::Data> ProcessAxisEvent(const uint8_t index,
std::vector<UserInputEvent> ProcessButtonEvent(const uint8_t index, const bool pressed); const JoyAxisStatus status);
std::vector<UserInputEvent> ProcessHatEvent(const uint8_t index, const uint8_t status); std::vector<UserInputEvent::Data> ProcessButtonEvent(const uint8_t index, const bool pressed);
std::vector<UserInputEvent::Data> ProcessHatEvent(const uint8_t index, const uint8_t status);
// Activates or deactivates rumble. // Activates or deactivates rumble.
void SetRumble(bool activate_rumble); void SetRumble(bool activate_rumble);
@ -192,14 +192,14 @@ bool JoyState::IsValid() const {
return sdl_joystick_; return sdl_joystick_;
} }
std::vector<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index, std::vector<UserInputEvent::Data> JoyState::ProcessAxisEvent(const uint8_t index,
const JoyAxisStatus status) { const JoyAxisStatus status) {
const JoyAxisStatus previous_status = axis_[index]; const JoyAxisStatus previous_status = axis_[index];
std::vector<UserInputEvent> events; std::vector<UserInputEvent::Data> event_data;
// Nothing to do if no-op. // Nothing to do if no-op.
if (status == previous_status) { if (status == previous_status) {
return events; return event_data;
} }
// Update the value. // Update the value.
@ -207,48 +207,50 @@ std::vector<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index,
if (previous_status != JoyAxisStatus::Neutral) { if (previous_status != JoyAxisStatus::Neutral) {
// Send the "unpressed" event. // Send the "unpressed" event.
events.push_back(UserInputEvent( event_data.emplace_back(
config::JoyInput(wx_joystick_, AxisStatusToJoyControl(previous_status), index), false)); config::JoyInput(wx_joystick_, AxisStatusToJoyControl(previous_status), index), false);
} }
// We already sent the "unpressed" event so nothing more to do. // We already sent the "unpressed" event so nothing more to do.
if (status == JoyAxisStatus::Neutral) { if (status == JoyAxisStatus::Neutral) {
return events; return event_data;
} }
// Send the "pressed" event. // Send the "pressed" event.
events.push_back(UserInputEvent( event_data.emplace_back(config::JoyInput(wx_joystick_, AxisStatusToJoyControl(status), index),
config::JoyInput(wx_joystick_, AxisStatusToJoyControl(status), index), true)); true);
return events; return event_data;
} }
std::vector<UserInputEvent> JoyState::ProcessButtonEvent(const uint8_t index, const bool status) { std::vector<UserInputEvent::Data> JoyState::ProcessButtonEvent(const uint8_t index,
const bool status) {
const bool previous_status = buttons_[index]; const bool previous_status = buttons_[index];
std::vector<UserInputEvent> events; std::vector<UserInputEvent::Data> event_data;
// Nothing to do if no-op. // Nothing to do if no-op.
if (status == previous_status) { if (status == previous_status) {
return events; return event_data;
} }
// Update the value. // Update the value.
buttons_[index] = status; buttons_[index] = status;
// Send the event. // Send the event.
events.push_back( event_data.emplace_back(config::JoyInput(wx_joystick_, config::JoyControl::Button, index),
UserInputEvent(config::JoyInput(wx_joystick_, config::JoyControl::Button, index), status)); status);
return events; return event_data;
} }
std::vector<UserInputEvent> JoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) { std::vector<UserInputEvent::Data> JoyState::ProcessHatEvent(const uint8_t index,
const uint8_t status) {
const uint16_t previous_status = hats_[index]; const uint16_t previous_status = hats_[index];
std::vector<UserInputEvent> events; std::vector<UserInputEvent::Data> event_data;
// Nothing to do if no-op. // Nothing to do if no-op.
if (status == previous_status) { if (status == previous_status) {
return events; return event_data;
} }
// Update the value. // Update the value.
@ -262,17 +264,17 @@ std::vector<UserInputEvent> JoyState::ProcessHatEvent(const uint8_t index, const
const bool new_control_pressed = (status & bit) != 0; const bool new_control_pressed = (status & bit) != 0;
if (old_control_pressed && !new_control_pressed) { if (old_control_pressed && !new_control_pressed) {
// Send the "unpressed" event. // Send the "unpressed" event.
events.push_back(UserInputEvent( event_data.emplace_back(
config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), false)); config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), false);
} }
if (!old_control_pressed && new_control_pressed) { if (!old_control_pressed && new_control_pressed) {
// Send the "pressed" event. // Send the "pressed" event.
events.push_back(UserInputEvent( event_data.emplace_back(
config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), true)); config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), true);
} }
} }
return events; return event_data;
} }
void JoyState::SetRumble(bool activate_rumble) { void JoyState::SetRumble(bool activate_rumble) {
@ -341,23 +343,23 @@ void SdlPoller::Notify() {
SDL_Event sdl_event; SDL_Event sdl_event;
while (SDL_PollEvent(&sdl_event)) { while (SDL_PollEvent(&sdl_event)) {
std::vector<UserInputEvent> events; std::vector<UserInputEvent::Data> event_data;
JoyState* joy_state = nullptr; JoyState* joy_state = nullptr;
switch (sdl_event.type) { switch (sdl_event.type) {
case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONUP:
joy_state = FindJoyState(sdl_event.cbutton.which); joy_state = FindJoyState(sdl_event.cbutton.which);
if (joy_state) { if (joy_state) {
events = joy_state->ProcessButtonEvent(sdl_event.cbutton.button, event_data = joy_state->ProcessButtonEvent(sdl_event.cbutton.button,
sdl_event.cbutton.state); sdl_event.cbutton.state);
} }
break; break;
case SDL_CONTROLLERAXISMOTION: case SDL_CONTROLLERAXISMOTION:
joy_state = FindJoyState(sdl_event.caxis.which); joy_state = FindJoyState(sdl_event.caxis.which);
if (joy_state) { if (joy_state) {
events = joy_state->ProcessAxisEvent(sdl_event.caxis.axis, event_data = joy_state->ProcessAxisEvent(
AxisValueToStatus(sdl_event.caxis.value)); sdl_event.caxis.axis, AxisValueToStatus(sdl_event.caxis.value));
} }
break; break;
@ -372,23 +374,24 @@ void SdlPoller::Notify() {
case SDL_JOYBUTTONUP: case SDL_JOYBUTTONUP:
joy_state = FindJoyState(sdl_event.jbutton.which); joy_state = FindJoyState(sdl_event.jbutton.which);
if (joy_state && !joy_state->is_game_controller()) { if (joy_state && !joy_state->is_game_controller()) {
events = joy_state->ProcessButtonEvent(sdl_event.jbutton.button, event_data = joy_state->ProcessButtonEvent(sdl_event.jbutton.button,
sdl_event.jbutton.state); sdl_event.jbutton.state);
} }
break; break;
case SDL_JOYAXISMOTION: case SDL_JOYAXISMOTION:
joy_state = FindJoyState(sdl_event.jaxis.which); joy_state = FindJoyState(sdl_event.jaxis.which);
if (joy_state && !joy_state->is_game_controller()) { if (joy_state && !joy_state->is_game_controller()) {
events = joy_state->ProcessAxisEvent(sdl_event.jaxis.axis, event_data = joy_state->ProcessAxisEvent(
AxisValueToStatus(sdl_event.jaxis.value)); sdl_event.jaxis.axis, AxisValueToStatus(sdl_event.jaxis.value));
} }
break; break;
case SDL_JOYHATMOTION: case SDL_JOYHATMOTION:
joy_state = FindJoyState(sdl_event.jhat.which); joy_state = FindJoyState(sdl_event.jhat.which);
if (joy_state && !joy_state->is_game_controller()) { if (joy_state && !joy_state->is_game_controller()) {
events = joy_state->ProcessHatEvent(sdl_event.jhat.hat, sdl_event.jhat.value); event_data =
joy_state->ProcessHatEvent(sdl_event.jhat.hat, sdl_event.jhat.value);
} }
break; break;
@ -402,12 +405,10 @@ void SdlPoller::Notify() {
break; break;
} }
if (!events.empty()) { if (!event_data.empty()) {
wxEvtHandler* handler = handler_provider_(); wxEvtHandler* handler = handler_provider_();
if (handler) { if (handler) {
for (const auto& user_input_event : events) { handler->QueueEvent(new UserInputEvent(std::move(event_data)));
handler->QueueEvent(user_input_event.Clone());
}
} }
} }
} }

View File

@ -31,9 +31,8 @@ bool UserInputCtrl::Create(wxWindow* parent,
long style, long style,
const wxString& name) { const wxString& name) {
this->SetClientObject(new UserInputEventSender(this)); this->SetClientObject(new UserInputEventSender(this));
this->Bind(VBAM_EVT_USER_INPUT_UP, &UserInputCtrl::OnUserInputUp, this); this->Bind(VBAM_EVT_USER_INPUT, &UserInputCtrl::OnUserInput, this);
this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) {
is_navigating_away_ = false;
last_focus_time_ = wxGetUTCTimeMillis(); last_focus_time_ = wxGetUTCTimeMillis();
event.Skip(); event.Skip();
}); });
@ -66,7 +65,15 @@ void UserInputCtrl::Clear() {
wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl); wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl);
void UserInputCtrl::OnUserInputUp(UserInputEvent& event) { void UserInputCtrl::OnUserInput(UserInputEvent& event) {
// Find the first pressed input.
nonstd::optional<config::UserInput> input = event.FirstReleasedInput();
if (input == nonstd::nullopt) {
// No pressed inputs.
return;
}
static const wxLongLong kInterval = 100; static const wxLongLong kInterval = 100;
if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) { if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) {
// Ignore events sent very shortly after focus. This is used to ignore // Ignore events sent very shortly after focus. This is used to ignore
@ -75,20 +82,13 @@ void UserInputCtrl::OnUserInputUp(UserInputEvent& event) {
return; return;
} }
if (is_navigating_away_) {
// Ignore events sent after the control has been navigated away from.
event.Skip();
return;
}
if (!is_multikey_) { if (!is_multikey_) {
inputs_.clear(); inputs_.clear();
} }
inputs_.insert(event.input()); inputs_.insert(std::move(input.value()));
UpdateText(); UpdateText();
Navigate(); Navigate();
is_navigating_away_ = true;
} }
void UserInputCtrl::UpdateText() { void UserInputCtrl::UpdateText() {

View File

@ -61,7 +61,7 @@ public:
private: private:
// Event handler. // Event handler.
void OnUserInputUp(widgets::UserInputEvent& event); void OnUserInput(widgets::UserInputEvent& event);
// Updates the text in the control to reflect the current inputs. // Updates the text in the control to reflect the current inputs.
void UpdateText(); void UpdateText();
@ -72,10 +72,6 @@ private:
// very shortly after activation. // very shortly after activation.
wxLongLong last_focus_time_ = 0; wxLongLong last_focus_time_ = 0;
// Set to true after one input has been received. This is used to ignore
// subsequent events until the control is focused again.
bool is_navigating_away_ = false;
std::unordered_set<config::UserInput> inputs_; std::unordered_set<config::UserInput> inputs_;
}; };

View File

@ -1,8 +1,10 @@
#include "wx/widgets/user-input-event.h" #include "wx/widgets/user-input-event.h"
#include <algorithm>
#include <vector> #include <vector>
#include <wx/event.h> #include <wx/event.h>
#include <wx/eventfilter.h>
#include <wx/window.h> #include <wx/window.h>
#include "wx/config/user-input.h" #include "wx/config/user-input.h"
@ -109,11 +111,44 @@ wxKeyCode KeyFromModifier(const wxKeyModifier mod) {
} // namespace } // namespace
UserInputEvent::UserInputEvent(const config::UserInput& input, bool pressed) UserInputEvent::UserInputEvent(std::vector<Data> event_data)
: wxEvent(0, pressed ? VBAM_EVT_USER_INPUT_DOWN : VBAM_EVT_USER_INPUT_UP), input_(input) {} : wxEvent(0, VBAM_EVT_USER_INPUT), data_(std::move(event_data)) {}
nonstd::optional<config::UserInput> UserInputEvent::FirstReleasedInput() const {
const auto iter =
std::find_if(data_.begin(), data_.end(), [](const auto& data) { return !data.pressed; });
if (iter == data_.end()) {
// No pressed inputs.
return nonstd::nullopt;
}
return iter->input;
}
int UserInputEvent::FilterProcessedInput(const config::UserInput& user_input) {
// Keep all data not using `user_input`.
std::vector<Data> new_data;
for (const auto& data : data_) {
if (data.input != user_input) {
new_data.push_back(data);
}
}
// Update the internal data.
data_ = std::move(new_data);
if (data_.empty()) {
// All data was removed, the event was fully processed.
return wxEventFilter::Event_Processed;
} else {
// Some data remains, let the event propagate.
return wxEventFilter::Event_Skip;
}
}
wxEvent* UserInputEvent::Clone() const { wxEvent* UserInputEvent::Clone() const {
return new UserInputEvent(*this); return new UserInputEvent(this->data_);
} }
UserInputEventSender::UserInputEventSender(wxWindow* const window) UserInputEventSender::UserInputEventSender(wxWindow* const window)
@ -161,26 +196,25 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
} }
const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_); const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_);
std::vector<config::KeyboardInput> new_inputs; std::vector<UserInputEvent::Data> event_data;
if (key_pressed == WXK_NONE) { if (key_pressed == WXK_NONE) {
// A new standalone modifier was pressed, send the event. // A new standalone modifier was pressed, send the event.
new_inputs.emplace_back(KeyFromModifier(mod_pressed), mod_pressed); event_data.emplace_back(config::KeyboardInput(KeyFromModifier(mod_pressed), mod_pressed),
true);
} else { } else {
// A new key was pressed, send the event with modifiers, first. // A new key was pressed, send the event with modifiers, first.
new_inputs.emplace_back(key, active_mods); event_data.emplace_back(config::KeyboardInput(key, active_mods), true);
if (active_mods != wxMOD_NONE) { if (active_mods != wxMOD_NONE) {
// Keep track of the key pressed with the active modifiers. // Keep track of the key pressed with the active modifiers.
active_mod_inputs_.emplace(key, active_mods); active_mod_inputs_.emplace(key, active_mods);
// Also send the key press event without modifiers. // Also send the key press event without modifiers.
new_inputs.emplace_back(key, wxMOD_NONE); event_data.emplace_back(config::KeyboardInput(key, wxMOD_NONE), true);
} }
} }
for (const config::KeyboardInput& input : new_inputs) { wxQueueEvent(window_, new UserInputEvent(std::move(event_data)));
wxQueueEvent(window_, new UserInputEvent(input, true));
}
} }
void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
@ -220,31 +254,32 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
return; return;
} }
std::vector<config::KeyboardInput> released_inputs; std::vector<UserInputEvent::Data> event_data;
if (key_released == WXK_NONE) { if (key_released == WXK_NONE) {
// A standalone modifier was released, send it. // A standalone modifier was released, send it.
released_inputs.emplace_back(KeyFromModifier(mod_released), mod_released); event_data.emplace_back(config::KeyboardInput(KeyFromModifier(mod_released), mod_released),
false);
} else { } else {
// A key was released. // A key was released.
if (previous_mods == wxMOD_NONE) { if (previous_mods == wxMOD_NONE) {
// The key was pressed without modifiers, just send the key release event. // The key was pressed without modifiers, just send the key release event.
released_inputs.emplace_back(key, wxMOD_NONE); event_data.emplace_back(config::KeyboardInput(key, wxMOD_NONE), false);
} else { } else {
// Check if the key was pressed with the active modifiers. // Check if the key was pressed with the active modifiers.
const config::KeyboardInput input_with_modifiers(key, previous_mods); const config::KeyboardInput input_with_modifiers(key, previous_mods);
auto iter = active_mod_inputs_.find(input_with_modifiers); auto iter = active_mod_inputs_.find(input_with_modifiers);
if (iter == active_mod_inputs_.end()) { if (iter == active_mod_inputs_.end()) {
// The key press event was never sent, so do it now. // The key press event was never sent, so do it now.
wxQueueEvent(window_, new UserInputEvent(input_with_modifiers, true)); event_data.emplace_back(input_with_modifiers, true);
} else { } else {
active_mod_inputs_.erase(iter); active_mod_inputs_.erase(iter);
} }
// Send the key release event with the active modifiers. // Send the key release event with the active modifiers.
released_inputs.emplace_back(key, previous_mods); event_data.emplace_back(config::KeyboardInput(key, previous_mods), false);
// Also send the key release event without modifiers. // Also send the key release event without modifiers.
released_inputs.emplace_back(key, wxMOD_NONE); event_data.emplace_back(config::KeyboardInput(key, wxMOD_NONE), false);
} }
} }
@ -255,15 +290,14 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
auto iter = active_mod_inputs_.find(input); auto iter = active_mod_inputs_.find(input);
if (iter != active_mod_inputs_.end()) { if (iter != active_mod_inputs_.end()) {
active_mod_inputs_.erase(iter); active_mod_inputs_.erase(iter);
released_inputs.push_back(std::move(input)); event_data.emplace_back(std::move(input), false);
} }
} }
for (const auto& data : event_data) {
for (const config::KeyboardInput& input : released_inputs) { active_mod_inputs_.erase(data.input.keyboard_input());
active_mod_inputs_.erase(input);
wxQueueEvent(window_, new UserInputEvent(input, false));
} }
wxQueueEvent(window_, new UserInputEvent(std::move(event_data)));
} }
void UserInputEventSender::Reset(wxFocusEvent& event) { void UserInputEventSender::Reset(wxFocusEvent& event) {
@ -278,5 +312,4 @@ void UserInputEventSender::Reset(wxFocusEvent& event) {
} // namespace widgets } // namespace widgets
wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); wxDEFINE_EVENT(VBAM_EVT_USER_INPUT, widgets::UserInputEvent);
wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent);

View File

@ -2,6 +2,9 @@
#define WX_WIDGETS_USER_INPUT_EVENT_H_ #define WX_WIDGETS_USER_INPUT_EVENT_H_
#include <unordered_set> #include <unordered_set>
#include <vector>
#include <optional.hpp>
#include <wx/clntdata.h> #include <wx/clntdata.h>
#include <wx/event.h> #include <wx/event.h>
@ -10,20 +13,43 @@
namespace widgets { namespace widgets {
// Event fired when a user input is pressed or released. The event contains the // Event fired when a set of user input are pressed or released. The event
// user input that was pressed or released. // contains the set of user input that were pressed or released. The order
// in the vector matters, this is the order in which the inputs were pressed or
// released.
class UserInputEvent final : public wxEvent { class UserInputEvent final : public wxEvent {
public: public:
UserInputEvent(const config::UserInput& input, bool pressed); // Data for the event. Contains the user input and whether it was pressed or
// released.
struct Data {
const config::UserInput input;
const bool pressed;
Data(config::UserInput input, bool pressed) : input(input), pressed(pressed){};
};
UserInputEvent(std::vector<Data> event_data);
virtual ~UserInputEvent() override = default; virtual ~UserInputEvent() override = default;
// Disable copy and copy assignment.
UserInputEvent(const UserInputEvent&) = delete;
UserInputEvent& operator=(const UserInputEvent&) = delete;
// Returns the first pressed input, if any.
nonstd::optional<config::UserInput> FirstReleasedInput() const;
// Mark `event_data` as processed and returns the new event filter. This is
// meant to be used with FilterEvent() to process global shortcuts before
// sending the event to the next handler.
int FilterProcessedInput(const config::UserInput& user_input);
// wxEvent implementation. // wxEvent implementation.
wxEvent* Clone() const override; wxEvent* Clone() const override;
const config::UserInput& input() const { return input_; } const std::vector<Data>& data() const { return data_; }
private: private:
const config::UserInput input_; std::vector<Data> data_;
}; };
// Object that is used to fire user input events when a key is pressed or // Object that is used to fire user input events when a key is pressed or
@ -59,9 +85,7 @@ private:
} // namespace widgets } // namespace widgets
// Fired when a user input is pressed. // Fired when a set of user inputs are pressed or released.
wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); wxDECLARE_EVENT(VBAM_EVT_USER_INPUT, widgets::UserInputEvent);
// Fired when a user input is released.
wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent);
#endif // WX_WIDGETS_USER_INPUT_EVENT_H_ #endif // WX_WIDGETS_USER_INPUT_EVENT_H_

View File

@ -1,5 +1,4 @@
#include "wx/wxvbam.h" #include "wx/wxvbam.h"
#include "wx/config/command.h"
#ifdef __WXMSW__ #ifdef __WXMSW__
#include <windows.h> #include <windows.h>
@ -35,6 +34,7 @@
#include "wx/builtin-over.h" #include "wx/builtin-over.h"
#include "wx/builtin-xrc.h" #include "wx/builtin-xrc.h"
#include "wx/config/cmdtab.h" #include "wx/config/cmdtab.h"
#include "wx/config/command.h"
#include "wx/config/emulated-gamepad.h" #include "wx/config/emulated-gamepad.h"
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
#include "wx/config/option.h" #include "wx/config/option.h"
@ -995,27 +995,46 @@ int MainFrame::FilterEvent(wxEvent& event) {
return wxEventFilter::Event_Skip; return wxEventFilter::Event_Skip;
} }
if (event.GetEventType() != VBAM_EVT_USER_INPUT_DOWN) { if (event.GetEventType() != VBAM_EVT_USER_INPUT) {
// We only treat "VBAM_EVT_USER_INPUT_DOWN" events here. // We only treat "VBAM_EVT_USER_INPUT" events here.
return wxEventFilter::Event_Skip; return wxEventFilter::Event_Skip;
} }
const widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event); widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event);
nonstd::optional<config::Command> command = const config::Bindings* bindings = wxGetApp().bindings();
wxGetApp().bindings()->CommandForInput(user_input_event.input()); int command_id = wxID_NONE;
if (command == nonstd::nullopt) { nonstd::optional<config::UserInput> user_input;
for (const auto& event_data : user_input_event.data()) {
if (!event_data.pressed) {
// We only treat key press events here.
continue;
}
const nonstd::optional<config::Command> command =
bindings->CommandForInput(event_data.input);
if (command != nonstd::nullopt && command->is_shortcut()) {
// Associated shortcut command found.
command_id = command->shortcut().id();
user_input.emplace(event_data.input);
break;
}
}
if (command_id == wxID_NONE) {
// No associated command found. // No associated command found.
return wxEventFilter::Event_Skip; return wxEventFilter::Event_Skip;
} }
if (!command->is_shortcut()) { // Execute the associated shortcut command.
return wxEventFilter::Event_Skip; wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command_id);
}
wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command->shortcut().id());
command_event.SetEventObject(this); command_event.SetEventObject(this);
this->GetEventHandler()->ProcessEvent(command_event); this->GetEventHandler()->ProcessEvent(command_event);
return wxEventFilter::Event_Processed;
// Filter out the processed input so it is not processed again. This also
// prevents us from firing 2 commands if the user presses "Ctrl+1" and has
// a shortcut for both "Ctrl+1" and "1". Only one will be handled here.
return user_input_event.FilterProcessedInput(user_input.value());
} }
wxString MainFrame::GetGamePath(wxString path) wxString MainFrame::GetGamePath(wxString path)

View File

@ -530,8 +530,7 @@ protected:
bool paused; bool paused;
void OnIdle(wxIdleEvent&); void OnIdle(wxIdleEvent&);
void OnUserInputDown(widgets::UserInputEvent& event); void OnUserInput(widgets::UserInputEvent& event);
void OnUserInputUp(widgets::UserInputEvent& event);
void PaintEv(wxPaintEvent& ev); void PaintEv(wxPaintEvent& ev);
void EraseBackground(wxEraseEvent& ev); void EraseBackground(wxEraseEvent& ev);
void OnSize(wxSizeEvent& ev); void OnSize(wxSizeEvent& ev);