From 902c6c8e4bf40cb3be38cbfdb1f571836526778f Mon Sep 17 00:00:00 2001 From: Fabrice de Gans Date: Sat, 4 May 2024 13:06:20 -0700 Subject: [PATCH] [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. --- src/wx/panel.cpp | 42 ++++++++------ src/wx/widgets/sdl-poller.cpp | 85 +++++++++++++++-------------- src/wx/widgets/user-input-ctrl.cpp | 22 ++++---- src/wx/widgets/user-input-ctrl.h | 6 +- src/wx/widgets/user-input-event.cpp | 79 +++++++++++++++++++-------- src/wx/widgets/user-input-event.h | 42 +++++++++++--- src/wx/wxvbam.cpp | 45 ++++++++++----- src/wx/wxvbam.h | 3 +- 8 files changed, 201 insertions(+), 123 deletions(-) diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index 0a1290d0..cd6365a3 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -1177,8 +1177,7 @@ void GameArea::OnIdle(wxIdleEvent& event) wxWindow* w = panel->GetWindow(); // set up event handlers - w->Bind(VBAM_EVT_USER_INPUT_DOWN, &GameArea::OnUserInputDown, this); - w->Bind(VBAM_EVT_USER_INPUT_UP, &GameArea::OnUserInputUp, this); + w->Bind(VBAM_EVT_USER_INPUT, &GameArea::OnUserInput, this); w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this); w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this); @@ -1330,26 +1329,33 @@ static Display* GetX11Display() { } #endif // __WXGTK__ -void GameArea::OnUserInputDown(widgets::UserInputEvent& event) { - if (wxGetApp().emulated_gamepad()->OnInputPressed(event.input())) { - wxWakeUpIdle(); - } -} - -void GameArea::OnUserInputUp(widgets::UserInputEvent& event) { - if (wxGetApp().emulated_gamepad()->OnInputReleased(event.input())) { - wxWakeUpIdle(); +void GameArea::OnUserInput(widgets::UserInputEvent& event) { + bool emulated_key_pressed = false; + for (const auto& event_data : event.data()) { + if (event_data.pressed) { + if (wxGetApp().emulated_gamepad()->OnInputPressed(event_data.input)) { + emulated_key_pressed = true; + } + } else { + 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 - // this shouldn't be necessary of course + if (emulated_key_pressed) { + wxWakeUpIdle(); + #if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS) - if (event.input().is_joystick() && !wxGetApp().UsingWayland()) { - auto display = GetX11Display(); - XResetScreenSaver(display); - XFlush(display); - } + // Tell X11 to turn off the screensaver/screen-blank if a button was + // was pressed. This shouldn't be necessary. + if (!wxGetApp().UsingWayland()) { + auto display = GetX11Display(); + XResetScreenSaver(display); + XFlush(display); + } #endif + } } // these three are forwarded to the DrawingPanel instance diff --git a/src/wx/widgets/sdl-poller.cpp b/src/wx/widgets/sdl-poller.cpp index 9f7031d9..c7194094 100644 --- a/src/wx/widgets/sdl-poller.cpp +++ b/src/wx/widgets/sdl-poller.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -86,9 +85,10 @@ public: bool is_game_controller() const { return !!game_controller_; } // Processes the corresponding events. - std::vector ProcessAxisEvent(const uint8_t index, const JoyAxisStatus status); - std::vector ProcessButtonEvent(const uint8_t index, const bool pressed); - std::vector ProcessHatEvent(const uint8_t index, const uint8_t status); + std::vector ProcessAxisEvent(const uint8_t index, + const JoyAxisStatus status); + std::vector ProcessButtonEvent(const uint8_t index, const bool pressed); + std::vector ProcessHatEvent(const uint8_t index, const uint8_t status); // Activates or deactivates rumble. void SetRumble(bool activate_rumble); @@ -192,14 +192,14 @@ bool JoyState::IsValid() const { return sdl_joystick_; } -std::vector JoyState::ProcessAxisEvent(const uint8_t index, - const JoyAxisStatus status) { +std::vector JoyState::ProcessAxisEvent(const uint8_t index, + const JoyAxisStatus status) { const JoyAxisStatus previous_status = axis_[index]; - std::vector events; + std::vector event_data; // Nothing to do if no-op. if (status == previous_status) { - return events; + return event_data; } // Update the value. @@ -207,48 +207,50 @@ std::vector JoyState::ProcessAxisEvent(const uint8_t index, if (previous_status != JoyAxisStatus::Neutral) { // Send the "unpressed" event. - events.push_back(UserInputEvent( - config::JoyInput(wx_joystick_, AxisStatusToJoyControl(previous_status), index), false)); + event_data.emplace_back( + config::JoyInput(wx_joystick_, AxisStatusToJoyControl(previous_status), index), false); } // We already sent the "unpressed" event so nothing more to do. if (status == JoyAxisStatus::Neutral) { - return events; + return event_data; } // Send the "pressed" event. - events.push_back(UserInputEvent( - config::JoyInput(wx_joystick_, AxisStatusToJoyControl(status), index), true)); + event_data.emplace_back(config::JoyInput(wx_joystick_, AxisStatusToJoyControl(status), index), + true); - return events; + return event_data; } -std::vector JoyState::ProcessButtonEvent(const uint8_t index, const bool status) { +std::vector JoyState::ProcessButtonEvent(const uint8_t index, + const bool status) { const bool previous_status = buttons_[index]; - std::vector events; + std::vector event_data; // Nothing to do if no-op. if (status == previous_status) { - return events; + return event_data; } // Update the value. buttons_[index] = status; // Send the event. - events.push_back( - UserInputEvent(config::JoyInput(wx_joystick_, config::JoyControl::Button, index), status)); + event_data.emplace_back(config::JoyInput(wx_joystick_, config::JoyControl::Button, index), + status); - return events; + return event_data; } -std::vector JoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) { +std::vector JoyState::ProcessHatEvent(const uint8_t index, + const uint8_t status) { const uint16_t previous_status = hats_[index]; - std::vector events; + std::vector event_data; // Nothing to do if no-op. if (status == previous_status) { - return events; + return event_data; } // Update the value. @@ -262,17 +264,17 @@ std::vector JoyState::ProcessHatEvent(const uint8_t index, const const bool new_control_pressed = (status & bit) != 0; if (old_control_pressed && !new_control_pressed) { // Send the "unpressed" event. - events.push_back(UserInputEvent( - config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), false)); + event_data.emplace_back( + config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), false); } if (!old_control_pressed && new_control_pressed) { // Send the "pressed" event. - events.push_back(UserInputEvent( - config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), true)); + event_data.emplace_back( + config::JoyInput(wx_joystick_, HatStatusToJoyControl(bit), index), true); } } - return events; + return event_data; } void JoyState::SetRumble(bool activate_rumble) { @@ -341,23 +343,23 @@ void SdlPoller::Notify() { SDL_Event sdl_event; while (SDL_PollEvent(&sdl_event)) { - std::vector events; + std::vector event_data; JoyState* joy_state = nullptr; switch (sdl_event.type) { case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: joy_state = FindJoyState(sdl_event.cbutton.which); if (joy_state) { - events = joy_state->ProcessButtonEvent(sdl_event.cbutton.button, - sdl_event.cbutton.state); + event_data = joy_state->ProcessButtonEvent(sdl_event.cbutton.button, + sdl_event.cbutton.state); } break; case SDL_CONTROLLERAXISMOTION: joy_state = FindJoyState(sdl_event.caxis.which); if (joy_state) { - events = joy_state->ProcessAxisEvent(sdl_event.caxis.axis, - AxisValueToStatus(sdl_event.caxis.value)); + event_data = joy_state->ProcessAxisEvent( + sdl_event.caxis.axis, AxisValueToStatus(sdl_event.caxis.value)); } break; @@ -372,23 +374,24 @@ void SdlPoller::Notify() { case SDL_JOYBUTTONUP: joy_state = FindJoyState(sdl_event.jbutton.which); if (joy_state && !joy_state->is_game_controller()) { - events = joy_state->ProcessButtonEvent(sdl_event.jbutton.button, - sdl_event.jbutton.state); + event_data = joy_state->ProcessButtonEvent(sdl_event.jbutton.button, + sdl_event.jbutton.state); } break; case SDL_JOYAXISMOTION: joy_state = FindJoyState(sdl_event.jaxis.which); if (joy_state && !joy_state->is_game_controller()) { - events = joy_state->ProcessAxisEvent(sdl_event.jaxis.axis, - AxisValueToStatus(sdl_event.jaxis.value)); + event_data = joy_state->ProcessAxisEvent( + sdl_event.jaxis.axis, AxisValueToStatus(sdl_event.jaxis.value)); } break; case SDL_JOYHATMOTION: joy_state = FindJoyState(sdl_event.jhat.which); 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; @@ -402,12 +405,10 @@ void SdlPoller::Notify() { break; } - if (!events.empty()) { + if (!event_data.empty()) { wxEvtHandler* handler = handler_provider_(); if (handler) { - for (const auto& user_input_event : events) { - handler->QueueEvent(user_input_event.Clone()); - } + handler->QueueEvent(new UserInputEvent(std::move(event_data))); } } } diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp index 869b90c5..85041b02 100644 --- a/src/wx/widgets/user-input-ctrl.cpp +++ b/src/wx/widgets/user-input-ctrl.cpp @@ -31,9 +31,8 @@ bool UserInputCtrl::Create(wxWindow* parent, long style, const wxString& name) { 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) { - is_navigating_away_ = false; last_focus_time_ = wxGetUTCTimeMillis(); event.Skip(); }); @@ -66,7 +65,15 @@ void UserInputCtrl::Clear() { wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl); -void UserInputCtrl::OnUserInputUp(UserInputEvent& event) { +void UserInputCtrl::OnUserInput(UserInputEvent& event) { + // Find the first pressed input. + nonstd::optional input = event.FirstReleasedInput(); + + if (input == nonstd::nullopt) { + // No pressed inputs. + return; + } + static const wxLongLong kInterval = 100; if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) { // Ignore events sent very shortly after focus. This is used to ignore @@ -75,20 +82,13 @@ void UserInputCtrl::OnUserInputUp(UserInputEvent& event) { return; } - if (is_navigating_away_) { - // Ignore events sent after the control has been navigated away from. - event.Skip(); - return; - } - if (!is_multikey_) { inputs_.clear(); } - inputs_.insert(event.input()); + inputs_.insert(std::move(input.value())); UpdateText(); Navigate(); - is_navigating_away_ = true; } void UserInputCtrl::UpdateText() { diff --git a/src/wx/widgets/user-input-ctrl.h b/src/wx/widgets/user-input-ctrl.h index eced50cc..191286f7 100644 --- a/src/wx/widgets/user-input-ctrl.h +++ b/src/wx/widgets/user-input-ctrl.h @@ -61,7 +61,7 @@ public: private: // Event handler. - void OnUserInputUp(widgets::UserInputEvent& event); + void OnUserInput(widgets::UserInputEvent& event); // Updates the text in the control to reflect the current inputs. void UpdateText(); @@ -72,10 +72,6 @@ private: // very shortly after activation. 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 inputs_; }; diff --git a/src/wx/widgets/user-input-event.cpp b/src/wx/widgets/user-input-event.cpp index db739f79..6723efb4 100644 --- a/src/wx/widgets/user-input-event.cpp +++ b/src/wx/widgets/user-input-event.cpp @@ -1,8 +1,10 @@ #include "wx/widgets/user-input-event.h" +#include #include #include +#include #include #include "wx/config/user-input.h" @@ -109,11 +111,44 @@ wxKeyCode KeyFromModifier(const wxKeyModifier mod) { } // namespace -UserInputEvent::UserInputEvent(const config::UserInput& input, bool pressed) - : wxEvent(0, pressed ? VBAM_EVT_USER_INPUT_DOWN : VBAM_EVT_USER_INPUT_UP), input_(input) {} +UserInputEvent::UserInputEvent(std::vector event_data) + : wxEvent(0, VBAM_EVT_USER_INPUT), data_(std::move(event_data)) {} + +nonstd::optional 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 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 { - return new UserInputEvent(*this); + return new UserInputEvent(this->data_); } UserInputEventSender::UserInputEventSender(wxWindow* const window) @@ -161,26 +196,25 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) { } const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_); - std::vector new_inputs; + std::vector event_data; if (key_pressed == WXK_NONE) { // 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 { // 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) { // Keep track of the key pressed with the active modifiers. active_mod_inputs_.emplace(key, active_mods); // 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(input, true)); - } + wxQueueEvent(window_, new UserInputEvent(std::move(event_data))); } void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { @@ -220,31 +254,32 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { return; } - std::vector released_inputs; + std::vector event_data; if (key_released == WXK_NONE) { // 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 { // A key was released. if (previous_mods == wxMOD_NONE) { // 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 { // Check if the key was pressed with the active modifiers. const config::KeyboardInput input_with_modifiers(key, previous_mods); auto iter = active_mod_inputs_.find(input_with_modifiers); if (iter == active_mod_inputs_.end()) { // 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 { active_mod_inputs_.erase(iter); } // 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. - 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); if (iter != active_mod_inputs_.end()) { active_mod_inputs_.erase(iter); - released_inputs.push_back(std::move(input)); + event_data.emplace_back(std::move(input), false); } } - - for (const config::KeyboardInput& input : released_inputs) { - active_mod_inputs_.erase(input); - wxQueueEvent(window_, new UserInputEvent(input, false)); + for (const auto& data : event_data) { + active_mod_inputs_.erase(data.input.keyboard_input()); } + wxQueueEvent(window_, new UserInputEvent(std::move(event_data))); } void UserInputEventSender::Reset(wxFocusEvent& event) { @@ -278,5 +312,4 @@ void UserInputEventSender::Reset(wxFocusEvent& event) { } // namespace widgets -wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); -wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent); +wxDEFINE_EVENT(VBAM_EVT_USER_INPUT, widgets::UserInputEvent); diff --git a/src/wx/widgets/user-input-event.h b/src/wx/widgets/user-input-event.h index f916294a..354011c6 100644 --- a/src/wx/widgets/user-input-event.h +++ b/src/wx/widgets/user-input-event.h @@ -2,6 +2,9 @@ #define WX_WIDGETS_USER_INPUT_EVENT_H_ #include +#include + +#include #include #include @@ -10,20 +13,43 @@ namespace widgets { -// Event fired when a user input is pressed or released. The event contains the -// user input that was pressed or released. +// Event fired when a set of user input are pressed or released. The event +// 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 { 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 event_data); 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 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* Clone() const override; - const config::UserInput& input() const { return input_; } + const std::vector& data() const { return data_; } private: - const config::UserInput input_; + std::vector data_; }; // Object that is used to fire user input events when a key is pressed or @@ -59,9 +85,7 @@ private: } // namespace widgets -// Fired when a user input is pressed. -wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); -// Fired when a user input is released. -wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent); +// Fired when a set of user inputs are pressed or released. +wxDECLARE_EVENT(VBAM_EVT_USER_INPUT, widgets::UserInputEvent); #endif // WX_WIDGETS_USER_INPUT_EVENT_H_ diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index 90e460d4..a165d453 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -1,5 +1,4 @@ #include "wx/wxvbam.h" -#include "wx/config/command.h" #ifdef __WXMSW__ #include @@ -35,6 +34,7 @@ #include "wx/builtin-over.h" #include "wx/builtin-xrc.h" #include "wx/config/cmdtab.h" +#include "wx/config/command.h" #include "wx/config/emulated-gamepad.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" @@ -995,27 +995,46 @@ int MainFrame::FilterEvent(wxEvent& event) { return wxEventFilter::Event_Skip; } - if (event.GetEventType() != VBAM_EVT_USER_INPUT_DOWN) { - // We only treat "VBAM_EVT_USER_INPUT_DOWN" events here. + if (event.GetEventType() != VBAM_EVT_USER_INPUT) { + // We only treat "VBAM_EVT_USER_INPUT" events here. return wxEventFilter::Event_Skip; } - const widgets::UserInputEvent& user_input_event = static_cast(event); - nonstd::optional command = - wxGetApp().bindings()->CommandForInput(user_input_event.input()); - if (command == nonstd::nullopt) { + widgets::UserInputEvent& user_input_event = static_cast(event); + const config::Bindings* bindings = wxGetApp().bindings(); + int command_id = wxID_NONE; + nonstd::optional 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 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. return wxEventFilter::Event_Skip; } - if (!command->is_shortcut()) { - return wxEventFilter::Event_Skip; - } - - wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command->shortcut().id()); + // Execute the associated shortcut command. + wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command_id); command_event.SetEventObject(this); 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) diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 5412e22b..1d5fd800 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -530,8 +530,7 @@ protected: bool paused; void OnIdle(wxIdleEvent&); - void OnUserInputDown(widgets::UserInputEvent& event); - void OnUserInputUp(widgets::UserInputEvent& event); + void OnUserInput(widgets::UserInputEvent& event); void PaintEv(wxPaintEvent& ev); void EraseBackground(wxEraseEvent& ev); void OnSize(wxSizeEvent& ev);