[UserInput] Filter key events globally
This changes key events to be filtered globally by the application, preventing any widget from accessing them. Widgets should instead use the new VBAM_USER_INPUT_EVENT event. In addition, this explicitly removes accelerator handling from wxWidgets main menu, allowing us to populate joystick options in the menu without firing wxWidgets assertions.
This commit is contained in:
parent
902c6c8e4b
commit
d543784a3d
|
@ -73,6 +73,7 @@ set(VBAM_WX_COMMON
|
|||
widgets/checkedlistctrl.h
|
||||
widgets/client-data.h
|
||||
widgets/dpi-support.h
|
||||
widgets/event-handler-provider.h
|
||||
widgets/group-check-box.cpp
|
||||
widgets/group-check-box.h
|
||||
widgets/keep-on-top-styler.cpp
|
||||
|
@ -87,6 +88,8 @@ set(VBAM_WX_COMMON
|
|||
widgets/user-input-event.h
|
||||
widgets/sdl-poller.cpp
|
||||
widgets/sdl-poller.h
|
||||
widgets/shortcut-menu-bar.cpp
|
||||
widgets/shortcut-menu-bar.h
|
||||
widgets/utils.cpp
|
||||
widgets/utils.h
|
||||
widgets/webupdatedef.h
|
||||
|
|
|
@ -230,12 +230,16 @@ JoyId JoyId::Invalid() {
|
|||
return JoyId(kInvalidSdlIndex);
|
||||
}
|
||||
|
||||
wxString JoyId::ToString() const {
|
||||
wxString JoyId::ToConfigString() const {
|
||||
return wxString::Format("Joy%d", sdl_index_ + 1);
|
||||
}
|
||||
|
||||
wxString JoyInput::ToString() const {
|
||||
const wxString joy_string = joy_.ToString();
|
||||
wxString JoyId::ToLocalizedString() const {
|
||||
return wxString::Format(_("Joystick %d"), sdl_index_ + 1);
|
||||
}
|
||||
|
||||
wxString JoyInput::ToConfigString() const {
|
||||
const wxString joy_string = joy_.ToConfigString();
|
||||
switch (control_) {
|
||||
case JoyControl::AxisPlus:
|
||||
return wxString::Format("%s-Axis%d+", joy_string, control_index_);
|
||||
|
@ -258,6 +262,30 @@ wxString JoyInput::ToString() const {
|
|||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString JoyInput::ToLocalizedString() const {
|
||||
const wxString joy_string = joy_.ToLocalizedString();
|
||||
switch (control_) {
|
||||
case JoyControl::AxisPlus:
|
||||
return wxString::Format(_("%s: Axis %d+"), joy_string, control_index_);
|
||||
case JoyControl::AxisMinus:
|
||||
return wxString::Format(_("%s: Axis %d-"), joy_string, control_index_);
|
||||
case JoyControl::Button:
|
||||
return wxString::Format(_("%s: Button %d"), joy_string, control_index_);
|
||||
case JoyControl::HatNorth:
|
||||
return wxString::Format(_("%s: Hat %d North"), joy_string, control_index_);
|
||||
case JoyControl::HatSouth:
|
||||
return wxString::Format(_("%s: Hat %d South"), joy_string, control_index_);
|
||||
case JoyControl::HatWest:
|
||||
return wxString::Format(_("%s: Hat %d West"), joy_string, control_index_);
|
||||
case JoyControl::HatEast:
|
||||
return wxString::Format(_("%s: Hat %d East"), joy_string, control_index_);
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
assert(false);
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString KeyboardInput::ToConfigString() const {
|
||||
// Handle the modifier case separately.
|
||||
if (KeyIsModifier(key_)) {
|
||||
|
@ -291,10 +319,6 @@ wxString KeyboardInput::ToLocalizedString() const {
|
|||
}
|
||||
|
||||
const wxString accel_string = wxAcceleratorEntry(mod_, key_).ToRawString().MakeUpper();
|
||||
if (!accel_string.IsAscii()) {
|
||||
// Unicode handling.
|
||||
return wxString::Format("%d:%d", key_, mod_);
|
||||
}
|
||||
return accel_string;
|
||||
}
|
||||
|
||||
|
@ -336,7 +360,7 @@ wxString UserInput::ToConfigString() const {
|
|||
case Device::Keyboard:
|
||||
return keyboard_input().ToConfigString();
|
||||
case Device::Joystick:
|
||||
return joy_input().ToString();
|
||||
return joy_input().ToConfigString();
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
|
@ -351,7 +375,7 @@ wxString UserInput::ToLocalizedString() const {
|
|||
case Device::Keyboard:
|
||||
return keyboard_input().ToLocalizedString();
|
||||
case Device::Joystick:
|
||||
return joy_input().ToString();
|
||||
return joy_input().ToLocalizedString();
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
|
|
|
@ -70,7 +70,8 @@ public:
|
|||
constexpr explicit JoyId(int sdl_index) : sdl_index_(sdl_index){};
|
||||
~JoyId() = default;
|
||||
|
||||
wxString ToString() const;
|
||||
wxString ToConfigString() const;
|
||||
wxString ToLocalizedString() const;
|
||||
|
||||
constexpr bool operator==(const JoyId& other) const { return sdl_index_ == other.sdl_index_; }
|
||||
constexpr bool operator!=(const JoyId& other) const { return sdl_index_ != other.sdl_index_; }
|
||||
|
@ -100,7 +101,8 @@ public:
|
|||
constexpr JoyControl control() const { return control_; }
|
||||
constexpr uint8_t control_index() const { return control_index_; }
|
||||
|
||||
wxString ToString() const;
|
||||
wxString ToConfigString() const;
|
||||
wxString ToLocalizedString() const;
|
||||
|
||||
constexpr bool operator==(const JoyInput& other) const {
|
||||
return joy_ == other.joy_ && control_ == other.control_ &&
|
||||
|
|
|
@ -165,7 +165,6 @@ GameArea::GameArea()
|
|||
config::OptionID::kSoundBuffers, config::OptionID::kSoundDSoundHWAccel,
|
||||
config::OptionID::kSoundUpmix},
|
||||
[&](config::Option*) { schedule_audio_restart_ = true; }) {
|
||||
this->SetClientObject(new widgets::UserInputEventSender(this));
|
||||
SetSizer(new wxBoxSizer(wxVERTICAL));
|
||||
// all renderers prefer 32-bit
|
||||
// well, "simple" prefers 24-bit, but that's not available for filters
|
||||
|
@ -1461,7 +1460,6 @@ DrawingPanel::DrawingPanel(wxWindow* parent, int _width, int _height)
|
|||
, wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(),
|
||||
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
|
||||
{
|
||||
this->SetClientObject(new widgets::UserInputEventSender(this));
|
||||
}
|
||||
|
||||
void DrawingPanelBase::DrawingPanelInit()
|
||||
|
@ -2193,7 +2191,6 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height)
|
|||
, wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(),
|
||||
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
|
||||
{
|
||||
this->SetClientObject(new widgets::UserInputEventSender(this));
|
||||
widgets::RequestHighResolutionOpenGlSurfaceForWindow(this);
|
||||
SetContext();
|
||||
}
|
||||
|
|
|
@ -1,38 +1,11 @@
|
|||
#include "wx/viewsupt.h"
|
||||
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/wxvbam.h"
|
||||
|
||||
namespace Viewers {
|
||||
|
||||
namespace {
|
||||
|
||||
int getKeyboardKeyCode(const wxKeyEvent& event) {
|
||||
const int key_code = event.GetKeyCode();
|
||||
if (key_code > WXK_START) {
|
||||
return key_code;
|
||||
}
|
||||
int uc = event.GetUnicodeKey();
|
||||
if (uc != WXK_NONE) {
|
||||
if (uc < 32) { // not all control chars
|
||||
switch (uc) {
|
||||
case WXK_BACK:
|
||||
case WXK_TAB:
|
||||
case WXK_RETURN:
|
||||
case WXK_ESCAPE:
|
||||
return uc;
|
||||
default:
|
||||
return WXK_NONE;
|
||||
}
|
||||
}
|
||||
return uc;
|
||||
} else {
|
||||
return event.GetKeyCode();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Viewer::CloseDlg(wxCloseEvent& ev)
|
||||
{
|
||||
(void)ev; // unused params
|
||||
|
@ -339,12 +312,11 @@ void MemView::Refit()
|
|||
NULL, this);
|
||||
disp.Connect(wxEVT_LEFT_UP, wxMouseEventHandler(MemView::MouseEvent),
|
||||
NULL, this);
|
||||
disp.Connect(wxEVT_CHAR, wxKeyEventHandler(MemView::KeyEvent),
|
||||
NULL, this);
|
||||
disp.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX));
|
||||
sb.Create(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
||||
wxSB_VERTICAL);
|
||||
sb.SetScrollbar(0, 15, 500, 15);
|
||||
disp.Bind(VBAM_EVT_USER_INPUT, &MemView::KeyEvent, this);
|
||||
}
|
||||
|
||||
wxClientDC dc(&disp);
|
||||
|
@ -443,17 +415,28 @@ void MemView::ShowCaret()
|
|||
disp.SetFocus();
|
||||
}
|
||||
|
||||
void MemView::KeyEvent(wxKeyEvent& ev)
|
||||
void MemView::KeyEvent(widgets::UserInputEvent& ev)
|
||||
{
|
||||
uint32_t k = getKeyboardKeyCode(ev);
|
||||
int nnib = 2 << fmt;
|
||||
const nonstd::optional<config::UserInput> opt_user_input = ev.FirstReleasedInput();
|
||||
if (opt_user_input == nonstd::nullopt) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (k) {
|
||||
const config::UserInput& user_input = opt_user_input.value();
|
||||
if (!user_input.is_keyboard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wxKeyCode key = user_input.keyboard_input().key();
|
||||
const wxKeyModifier mod = user_input.keyboard_input().mod();
|
||||
|
||||
int nnib = 2 << fmt;
|
||||
switch (key) {
|
||||
case WXK_RIGHT:
|
||||
case WXK_NUMPAD_RIGHT:
|
||||
if (isasc)
|
||||
selnib += 2;
|
||||
else if (ev.GetModifiers() == wxMOD_SHIFT)
|
||||
else if (mod == wxMOD_SHIFT)
|
||||
selnib += 2 << fmt;
|
||||
else if (!(selnib % nnib))
|
||||
selnib += nnib + nnib - 1;
|
||||
|
@ -475,7 +458,7 @@ void MemView::KeyEvent(wxKeyEvent& ev)
|
|||
case WXK_NUMPAD_LEFT:
|
||||
if (isasc)
|
||||
selnib -= 2;
|
||||
else if (ev.GetModifiers() == wxMOD_SHIFT)
|
||||
else if (mod == wxMOD_SHIFT)
|
||||
selnib -= 2 << fmt;
|
||||
else if (!(++selnib % nnib))
|
||||
selnib -= nnib * 2;
|
||||
|
@ -506,7 +489,7 @@ void MemView::KeyEvent(wxKeyEvent& ev)
|
|||
break;
|
||||
|
||||
default:
|
||||
if (k > 0x7f || (isasc && !isprint(k)) || (!isasc && !isxdigit(k))) {
|
||||
if (key > 0x7f || (isasc && !isprint(key)) || (!isasc && !isxdigit(key))) {
|
||||
ev.Skip();
|
||||
return;
|
||||
}
|
||||
|
@ -537,10 +520,10 @@ void MemView::KeyEvent(wxKeyEvent& ev)
|
|||
|
||||
if (isasc) {
|
||||
mask = 0xff << bno * 8;
|
||||
val = k << bno * 8;
|
||||
val = key << bno * 8;
|
||||
} else {
|
||||
mask = 8 * (0xf << bno) + 4 * nibno;
|
||||
val = isdigit(k) ? k - '0' : tolower(k) + 10 - 'a';
|
||||
val = isdigit(key) ? key - '0' : tolower(key) + 10 - 'a';
|
||||
val <<= bno * 8 + nibno * 4;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef VBAM_WX_VIEWSUPT_H_
|
||||
#define VBAM_WX_VIEWSUPT_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/image.h>
|
||||
|
@ -14,7 +16,7 @@
|
|||
#include <wx/stattext.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include <stdint.h> // for uint32_t
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
// avoid exporting too much stuff
|
||||
namespace Viewers {
|
||||
|
@ -218,7 +220,7 @@ protected:
|
|||
int addrlen;
|
||||
|
||||
void MouseEvent(wxMouseEvent& ev);
|
||||
void KeyEvent(wxKeyEvent& ev);
|
||||
void KeyEvent(widgets::UserInputEvent& ev);
|
||||
// the subwidgets
|
||||
wxPanel disp;
|
||||
wxScrollBar sb;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_
|
||||
#define VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_
|
||||
|
||||
// Forward declaration.
|
||||
class wxEvtHandler;
|
||||
|
||||
namespace widgets {
|
||||
|
||||
// Abstract interface to provide the currently active event handler to use for
|
||||
// user input events.
|
||||
class EventHandlerProvider {
|
||||
public:
|
||||
virtual ~EventHandlerProvider() = default;
|
||||
|
||||
// Returns the currently active event handler to use for user input events.
|
||||
virtual wxEvtHandler* event_handler() = 0;
|
||||
};
|
||||
|
||||
} // namespace widgets
|
||||
|
||||
#endif // VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_
|
|
@ -300,12 +300,14 @@ void JoyState::Notify() {
|
|||
SetRumble(rumbling_);
|
||||
}
|
||||
|
||||
SdlPoller::SdlPoller(const EventHandlerProvider handler_provider)
|
||||
SdlPoller::SdlPoller(EventHandlerProvider* const handler_provider)
|
||||
: enable_game_controller_(OPTION(kSDLGameControllerMode)),
|
||||
handler_provider_(handler_provider),
|
||||
game_controller_enabled_observer_(
|
||||
config::OptionID::kSDLGameControllerMode,
|
||||
[this](config::Option* option) { ReconnectControllers(option->GetBool()); }) {
|
||||
assert(handler_provider);
|
||||
|
||||
wxTimer::Start(50);
|
||||
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
|
||||
SDL_GameControllerEventState(SDL_ENABLE);
|
||||
|
@ -406,7 +408,7 @@ void SdlPoller::Notify() {
|
|||
}
|
||||
|
||||
if (!event_data.empty()) {
|
||||
wxEvtHandler* handler = handler_provider_();
|
||||
wxEvtHandler* handler = handler_provider_->event_handler();
|
||||
if (handler) {
|
||||
handler->QueueEvent(new UserInputEvent(std::move(event_data)));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#ifndef WX_WIDGETS_SDL_POLLER_H_
|
||||
#define WX_WIDGETS_SDL_POLLER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#include <wx/timer.h>
|
||||
|
@ -9,6 +8,7 @@
|
|||
#include <SDL.h>
|
||||
|
||||
#include "wx/config/option-observer.h"
|
||||
#include "wx/widgets/event-handler-provider.h"
|
||||
|
||||
// Forward declarations.
|
||||
class wxEvtHandler;
|
||||
|
@ -18,16 +18,13 @@ namespace widgets {
|
|||
// Forward declaration.
|
||||
class JoyState;
|
||||
|
||||
// Provider for the event handler to send the events to.
|
||||
using EventHandlerProvider = std::function<wxEvtHandler*()>;
|
||||
|
||||
// The SDL worker is responsible for handling SDL events and firing the
|
||||
// appropriate `UserInputEvent` for joysticks.
|
||||
// It is used to fire `UserInputEvent` for joysticks. This class should be kept
|
||||
// as a singleton owned by the application object.
|
||||
class SdlPoller final : public wxTimer {
|
||||
public:
|
||||
explicit SdlPoller(const EventHandlerProvider handler_provider);
|
||||
explicit SdlPoller(EventHandlerProvider* const handler_provider);
|
||||
~SdlPoller() final;
|
||||
|
||||
// Disable copy and copy assignment.
|
||||
|
@ -59,7 +56,7 @@ private:
|
|||
bool enable_game_controller_ = false;
|
||||
|
||||
// The provider of event handlers to send the events to.
|
||||
const EventHandlerProvider handler_provider_;
|
||||
EventHandlerProvider* const handler_provider_;
|
||||
|
||||
// Observer for the game controller enabled option.
|
||||
const config::OptionsObserver game_controller_enabled_observer_;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#include "wx/widgets/shortcut-menu-bar.h"
|
||||
|
||||
#include <wx/accel.h>
|
||||
|
||||
namespace widgets {
|
||||
|
||||
void ShortcutMenuBar::SetAcceleratorTable(const wxAcceleratorTable& /*accel*/) {
|
||||
// Do nothing. We don't want to set up accelerators on the menu bar.
|
||||
wxMenuBar::SetAcceleratorTable(wxNullAcceleratorTable);
|
||||
}
|
||||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(ShortcutMenuBar, wxMenuBar);
|
||||
|
||||
} // namespace widgets
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_
|
||||
#define VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_
|
||||
|
||||
#include <wx/menu.h>
|
||||
|
||||
namespace widgets {
|
||||
|
||||
// A menu bar with no accelerator table. This is used to prevent the menu bar
|
||||
// from stealing keyboard shortcuts from the main window.
|
||||
class ShortcutMenuBar : public wxMenuBar {
|
||||
public:
|
||||
ShortcutMenuBar() = default;
|
||||
~ShortcutMenuBar() override = default;
|
||||
|
||||
// wxMenuBar implementation.
|
||||
void SetAcceleratorTable(const wxAcceleratorTable& accel) override;
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(ShortcutMenuBar);
|
||||
};
|
||||
|
||||
} // namespace widgets
|
||||
|
||||
#endif // VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_
|
|
@ -30,7 +30,6 @@ bool UserInputCtrl::Create(wxWindow* parent,
|
|||
const wxSize& size,
|
||||
long style,
|
||||
const wxString& name) {
|
||||
this->SetClientObject(new UserInputEventSender(this));
|
||||
this->Bind(VBAM_EVT_USER_INPUT, &UserInputCtrl::OnUserInput, this);
|
||||
this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) {
|
||||
last_focus_time_ = wxGetUTCTimeMillis();
|
||||
|
|
|
@ -151,20 +151,27 @@ wxEvent* UserInputEvent::Clone() const {
|
|||
return new UserInputEvent(this->data_);
|
||||
}
|
||||
|
||||
UserInputEventSender::UserInputEventSender(wxWindow* const window)
|
||||
: window_(window) {
|
||||
assert(window);
|
||||
|
||||
// Attach the event handlers.
|
||||
window_->Bind(wxEVT_KEY_DOWN, &UserInputEventSender::OnKeyDown, this);
|
||||
window_->Bind(wxEVT_KEY_UP, &UserInputEventSender::OnKeyUp, this);
|
||||
window_->Bind(wxEVT_SET_FOCUS, &UserInputEventSender::Reset, this, window_->GetId());
|
||||
window_->Bind(wxEVT_KILL_FOCUS, &UserInputEventSender::Reset, this, window_->GetId());
|
||||
KeyboardInputSender::KeyboardInputSender(EventHandlerProvider* const handler_provider)
|
||||
: handler_provider_(handler_provider) {
|
||||
assert(handler_provider_);
|
||||
}
|
||||
|
||||
UserInputEventSender::~UserInputEventSender() = default;
|
||||
KeyboardInputSender::~KeyboardInputSender() = default;
|
||||
|
||||
void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
|
||||
void KeyboardInputSender::ProcessKeyEvent(wxKeyEvent& event) {
|
||||
if (!handler_provider_->event_handler()) {
|
||||
// No event handler to send the event to.
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.GetEventType() == wxEVT_KEY_DOWN) {
|
||||
OnKeyDown(event);
|
||||
} else if (event.GetEventType() == wxEVT_KEY_UP) {
|
||||
OnKeyUp(event);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardInputSender::OnKeyDown(wxKeyEvent& event) {
|
||||
// Stop propagation of the event.
|
||||
event.Skip(false);
|
||||
|
||||
|
@ -214,10 +221,10 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
|
|||
}
|
||||
}
|
||||
|
||||
wxQueueEvent(window_, new UserInputEvent(std::move(event_data)));
|
||||
wxQueueEvent(handler_provider_->event_handler(), new UserInputEvent(std::move(event_data)));
|
||||
}
|
||||
|
||||
void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
|
||||
void KeyboardInputSender::OnKeyUp(wxKeyEvent& event) {
|
||||
// Stop propagation of the event.
|
||||
event.Skip(false);
|
||||
|
||||
|
@ -297,17 +304,8 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
|
|||
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) {
|
||||
// Reset the internal state.
|
||||
active_keys_.clear();
|
||||
active_mods_.clear();
|
||||
active_mod_inputs_.clear();
|
||||
|
||||
// Let the event propagate.
|
||||
event.Skip();
|
||||
wxQueueEvent(handler_provider_->event_handler(), new UserInputEvent(std::move(event_data)));
|
||||
}
|
||||
|
||||
} // namespace widgets
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <wx/event.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/widgets/event-handler-provider.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
|
@ -53,34 +54,32 @@ private:
|
|||
};
|
||||
|
||||
// Object that is used to fire user input events when a key is pressed or
|
||||
// released. To use this object, attach it to a wxWindow and listen for
|
||||
// the VBAM_EVT_USER_INPUT_DOWN and VBAM_EVT_USER_INPUT_UP events.
|
||||
class UserInputEventSender final : public wxClientData {
|
||||
// released. This class should be kept as a singleton owned by the application
|
||||
// object. It is meant to be used in the FilterEvent() method of the app.
|
||||
class KeyboardInputSender final : public wxClientData {
|
||||
public:
|
||||
// `window` must not be nullptr. Will assert otherwise.
|
||||
// `window` must outlive this object.
|
||||
explicit UserInputEventSender(wxWindow* const window);
|
||||
~UserInputEventSender() override;
|
||||
explicit KeyboardInputSender(EventHandlerProvider* const handler_provider);
|
||||
~KeyboardInputSender() override;
|
||||
|
||||
// Disable copy and copy assignment.
|
||||
UserInputEventSender(const UserInputEventSender&) = delete;
|
||||
UserInputEventSender& operator=(const UserInputEventSender&) = delete;
|
||||
KeyboardInputSender(const KeyboardInputSender&) = delete;
|
||||
KeyboardInputSender& operator=(const KeyboardInputSender&) = delete;
|
||||
|
||||
// Processes the provided key event and sends the appropriate user input
|
||||
// event to the current event handler.
|
||||
void ProcessKeyEvent(wxKeyEvent& event);
|
||||
|
||||
private:
|
||||
// Keyboard event handlers.
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
void OnKeyUp(wxKeyEvent& event);
|
||||
|
||||
// Resets the internal state. Called on focus in/out.
|
||||
void Reset(wxFocusEvent& event);
|
||||
|
||||
std::unordered_set<wxKeyCode> active_keys_;
|
||||
std::unordered_set<wxKeyModifier> active_mods_;
|
||||
std::unordered_set<config::KeyboardInput> active_mod_inputs_;
|
||||
|
||||
// The wxWindow this object is attached to.
|
||||
// Must outlive this object.
|
||||
wxWindow* const window_;
|
||||
// The provider of event handlers to send the events to.
|
||||
EventHandlerProvider* const handler_provider_;
|
||||
};
|
||||
|
||||
} // namespace widgets
|
||||
|
|
|
@ -64,11 +64,6 @@ void ResetMenuItemAccelerator(wxMenuItem* menu_item) {
|
|||
wxGetApp().bindings()->InputsForCommand(
|
||||
config::ShortcutCommand(menu_item->GetId()));
|
||||
for (const config::UserInput& user_input : user_inputs) {
|
||||
if (user_input.device() != config::UserInput::Device::Keyboard) {
|
||||
// Cannot use joystick keybinding as text without wx assertion error.
|
||||
continue;
|
||||
}
|
||||
|
||||
new_label.append('\t');
|
||||
new_label.append(user_input.ToLocalizedString());
|
||||
break;
|
||||
|
@ -252,7 +247,8 @@ wxvbamApp::wxvbamApp()
|
|||
frame(nullptr),
|
||||
using_wayland(false),
|
||||
emulated_gamepad_(std::bind(&wxvbamApp::bindings, this)),
|
||||
sdl_poller_(std::bind(&wxvbamApp::GetJoyEventHandler, this)) {}
|
||||
sdl_poller_(this),
|
||||
keyboard_input_sender_(this) {}
|
||||
|
||||
const wxString wxvbamApp::GetPluginsDir()
|
||||
{
|
||||
|
@ -826,7 +822,7 @@ wxvbamApp::~wxvbamApp() {
|
|||
#endif
|
||||
}
|
||||
|
||||
wxEvtHandler* wxvbamApp::GetJoyEventHandler() {
|
||||
wxEvtHandler* wxvbamApp::event_handler() {
|
||||
// Use the active window, if any.
|
||||
wxWindow* focused_window = wxWindow::FindFocus();
|
||||
if (focused_window) {
|
||||
|
@ -842,7 +838,7 @@ wxEvtHandler* wxvbamApp::GetJoyEventHandler() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (OPTION(kUIAllowJoystickBackgroundInput)) {
|
||||
if (OPTION(kUIAllowJoystickBackgroundInput) || OPTION(kUIAllowKeyboardBackgroundInput)) {
|
||||
// Use the game panel, if the background polling option is enabled.
|
||||
return panel->panel->GetWindow()->GetEventHandler();
|
||||
}
|
||||
|
@ -990,53 +986,6 @@ void MainFrame::OnSize(wxSizeEvent& event)
|
|||
OPTION(kGeomFullScreen) = IsFullScreen();
|
||||
}
|
||||
|
||||
int MainFrame::FilterEvent(wxEvent& event) {
|
||||
if (menus_opened || dialog_opened) {
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
if (event.GetEventType() != VBAM_EVT_USER_INPUT) {
|
||||
// We only treat "VBAM_EVT_USER_INPUT" events here.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event);
|
||||
const config::Bindings* bindings = wxGetApp().bindings();
|
||||
int command_id = wxID_NONE;
|
||||
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.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
// Execute the associated shortcut command.
|
||||
wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command_id);
|
||||
command_event.SetEventObject(this);
|
||||
this->GetEventHandler()->ProcessEvent(command_event);
|
||||
|
||||
// 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 game_path = path;
|
||||
|
@ -1376,15 +1325,57 @@ void MainFrame::IdentifyRom()
|
|||
}
|
||||
}
|
||||
|
||||
// global event filter
|
||||
// apparently required for win32; just setting accel table still misses
|
||||
// a few keys (e.g. only ctrl-x works for exit, but not esc & ctrl-q;
|
||||
// ctrl-w does not work for close). It's possible another entity is
|
||||
// grabbing those keys, but I can't track it down.
|
||||
int wxvbamApp::FilterEvent(wxEvent& event)
|
||||
{
|
||||
if (frame) {
|
||||
return frame->FilterEvent(event);
|
||||
if (!frame) {
|
||||
// Ignore early events.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
return wxApp::FilterEvent(event);
|
||||
|
||||
if (event.GetEventType() == wxEVT_KEY_DOWN || event.GetEventType() == wxEVT_KEY_UP) {
|
||||
// Handle keyboard input events here. No control will receive them.
|
||||
keyboard_input_sender_.ProcessKeyEvent(static_cast<wxKeyEvent&>(event));
|
||||
return wxEventFilter::Event_Processed;
|
||||
}
|
||||
|
||||
if (!frame->CanProcessShortcuts()) {
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
if (event.GetEventType() != VBAM_EVT_USER_INPUT) {
|
||||
// We only treat "VBAM_EVT_USER_INPUT" events here.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event);
|
||||
int command_id = wxID_NONE;
|
||||
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.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
// Queue the associated shortcut command.
|
||||
wxCommandEvent* command_event = new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED, command_id);
|
||||
command_event->SetEventObject(this);
|
||||
frame->GetEventHandler()->QueueEvent(command_event);
|
||||
|
||||
return user_input_event.FilterProcessedInput(user_input.value());
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "wx/config/option.h"
|
||||
#include "wx/dialogs/base-dialog.h"
|
||||
#include "wx/widgets/dpi-support.h"
|
||||
#include "wx/widgets/event-handler-provider.h"
|
||||
#include "wx/widgets/keep-on-top-styler.h"
|
||||
#include "wx/widgets/sdl-poller.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
@ -59,7 +60,7 @@ inline std::string ToString(const wxChar* aString)
|
|||
|
||||
class MainFrame;
|
||||
|
||||
class wxvbamApp final : public wxApp {
|
||||
class wxvbamApp final : public wxApp, public widgets::EventHandlerProvider {
|
||||
public:
|
||||
wxvbamApp();
|
||||
|
||||
|
@ -133,8 +134,8 @@ protected:
|
|||
int console_status = 0;
|
||||
|
||||
private:
|
||||
// Returns the currently active event handler to use for user input events.
|
||||
wxEvtHandler* GetJoyEventHandler();
|
||||
// EventHandlerProvider implementation.
|
||||
wxEvtHandler* event_handler() override;
|
||||
|
||||
config::Bindings bindings_;
|
||||
config::EmulatedGamepad emulated_gamepad_;
|
||||
|
@ -143,6 +144,7 @@ private:
|
|||
char* home = nullptr;
|
||||
|
||||
widgets::SdlPoller sdl_poller_;
|
||||
widgets::KeyboardInputSender keyboard_input_sender_;
|
||||
|
||||
// Main configuration file.
|
||||
wxFileName config_file_;
|
||||
|
@ -200,8 +202,6 @@ public:
|
|||
void GetMenuOptionBool(const wxString& menuName, bool* field);
|
||||
void SetMenuOption(const wxString& menuName, bool value);
|
||||
|
||||
int FilterEvent(wxEvent& event);
|
||||
|
||||
GameArea* GetPanel()
|
||||
{
|
||||
return panel;
|
||||
|
@ -209,6 +209,8 @@ public:
|
|||
|
||||
wxString GetGamePath(wxString path);
|
||||
|
||||
bool CanProcessShortcuts() const { return !menus_opened && !dialog_opened; }
|
||||
|
||||
// wxMSW pauses the game for menu popups and modal dialogs, but wxGTK
|
||||
// does not. It's probably desirable to pause the game. To do this for
|
||||
// dialogs, use this function instead of dlg->ShowModal()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<resource xmlns="http://www.wxwidgets.org/wxxrc" version="2.5.3.0">
|
||||
<object class="wxMenuBar" name="MainMenu">
|
||||
<object class="wxMenuBar" name="MainMenu" subclass="ShortcutMenuBar">
|
||||
<object class="wxMenu">
|
||||
<label>_File</label>
|
||||
<object class="wxMenuItem" name="wxID_OPEN">
|
||||
|
|
Loading…
Reference in New Issue