[Input] Unify UserInput event handling
This adds a custom UserInputEvent for handling joypad and keyboard input for both accelerators and emulator control configuration. These changes fix a number of issues with the wxWidgets implementation of key down / up event handling. In particular, every "down" event now has a corresponding "up" event. All of the special handling that was done in multiple places to handle shortcuts is now done in a new class, `UserInputEventSender`, simplifying multiple call sites. This is another step towards complete unification of UserInput handling, which will prevent double assignment between shortcuts and emulator controls. Bug: #745
This commit is contained in:
parent
cc65ef2849
commit
1e1a369c8d
|
@ -78,8 +78,10 @@ set(VBAM_WX_COMMON
|
|||
widgets/render-plugin.h
|
||||
widgets/user-input-ctrl.cpp
|
||||
widgets/user-input-ctrl.h
|
||||
widgets/sdljoy.cpp
|
||||
widgets/sdljoy.h
|
||||
widgets/user-input-event.cpp
|
||||
widgets/user-input-event.h
|
||||
widgets/sdl-poller.cpp
|
||||
widgets/sdl-poller.h
|
||||
widgets/utils.cpp
|
||||
widgets/utils.h
|
||||
widgets/webupdatedef.h
|
||||
|
@ -87,8 +89,6 @@ set(VBAM_WX_COMMON
|
|||
widgets/wxmisc.cpp
|
||||
wxhead.h
|
||||
wxlogdebug.h
|
||||
wxutil.cpp
|
||||
wxutil.h
|
||||
wxvbam.cpp
|
||||
wxvbam.h
|
||||
x11keymap.h
|
||||
|
|
|
@ -547,17 +547,17 @@ wxThread::ExitCode BackgroundInput::CheckKeyboard()
|
|||
// virtual key "i" is pressed
|
||||
if ((bits & 0x8000) && (previousState[i] & 0x8000) == 0) {
|
||||
if (handler && !wxWindow::FindFocus()) {
|
||||
wxKeyEvent ev(wxEVT_KEY_DOWN);
|
||||
ev.m_keyCode = xKeySym;
|
||||
handler->AddPendingEvent(ev);
|
||||
wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_DOWN);
|
||||
event->m_keyCode = xKeySym;
|
||||
handler->QueueEvent(event);
|
||||
}
|
||||
}
|
||||
// virtual key "i" is released
|
||||
else if (((bits & 0x8000) == 0) && (previousState[i] & 0x8000)) {
|
||||
if (handler && !wxWindow::FindFocus()) {
|
||||
wxKeyEvent ev(wxEVT_KEY_UP);
|
||||
ev.m_keyCode = xKeySym;
|
||||
handler->AddPendingEvent(ev);
|
||||
wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_UP);
|
||||
event->m_keyCode = xKeySym;
|
||||
handler->QueueEvent(event);
|
||||
}
|
||||
}
|
||||
previousState[i] = bits;
|
||||
|
|
|
@ -1184,7 +1184,7 @@ EVT_HANDLER(AllowKeyboardBackgroundInput, "Allow keyboard background input (togg
|
|||
disableKeyboardBackgroundInput();
|
||||
if (OPTION(kUIAllowKeyboardBackgroundInput)) {
|
||||
if (panel && panel->panel) {
|
||||
enableKeyboardBackgroundInput(panel->panel->GetWindow());
|
||||
enableKeyboardBackgroundInput(panel->panel->GetWindow()->GetEventHandler());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2173,44 +2173,17 @@ EVT_HANDLER(EmulatorDirectories, "Directories...")
|
|||
|
||||
EVT_HANDLER(JoypadConfigure, "Joypad options...")
|
||||
{
|
||||
joy.PollAllJoysticks();
|
||||
|
||||
auto frame = wxGetApp().frame;
|
||||
bool joy_timer = frame->IsJoyPollTimerRunning();
|
||||
|
||||
if (!joy_timer) {
|
||||
frame->StartJoyPollTimer();
|
||||
}
|
||||
|
||||
if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) {
|
||||
update_joypad_opts();
|
||||
}
|
||||
|
||||
if (!joy_timer) {
|
||||
frame->StopJoyPollTimer();
|
||||
}
|
||||
|
||||
SetJoystick();
|
||||
}
|
||||
|
||||
EVT_HANDLER(Customize, "Customize UI...")
|
||||
{
|
||||
wxDialog* dlg = GetXRCDialog("AccelConfig");
|
||||
joy.PollAllJoysticks();
|
||||
|
||||
auto frame = wxGetApp().frame;
|
||||
bool joy_timer = frame->IsJoyPollTimerRunning();
|
||||
|
||||
if (!joy_timer) frame->StartJoyPollTimer();
|
||||
|
||||
if (ShowModal(dlg) == wxID_OK) {
|
||||
if (ShowModal(GetXRCDialog("AccelConfig")) == wxID_OK) {
|
||||
update_shortcut_opts();
|
||||
ResetMenuAccelerators();
|
||||
}
|
||||
|
||||
if (!joy_timer) frame->StopJoyPollTimer();
|
||||
|
||||
SetJoystick();
|
||||
}
|
||||
|
||||
#ifndef NO_ONLINEUPDATES
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "wx/opts.h"
|
||||
#include "wx/strutils.h"
|
||||
#include "wx/wxlogdebug.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
#define VBAM_WX_CONFIG_GAME_CONTROL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
|
|
@ -87,16 +87,6 @@ int Shortcuts::CommandForInput(const UserInput& input) const {
|
|||
return iter->second;
|
||||
}
|
||||
|
||||
std::set<wxJoystick> Shortcuts::Joysticks() const {
|
||||
std::set<wxJoystick> output;
|
||||
for (const auto& iter : command_to_inputs_) {
|
||||
for (const UserInput& user_input : iter.second) {
|
||||
output.insert(user_input.joystick());
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Shortcuts Shortcuts::Clone() const {
|
||||
return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
// by default, only 9 recent items
|
||||
// wxWidgets only goes up to `wxID_FILE9` but we want 10 recent files.
|
||||
#define wxID_FILE10 (wxID_FILE9 + 1)
|
||||
|
||||
namespace config {
|
||||
|
@ -45,9 +45,6 @@ public:
|
|||
// Returns the command currently assigned to `input` or nullptr if none.
|
||||
int CommandForInput(const UserInput& input) const;
|
||||
|
||||
// Returns the set of joysticks used by shortcuts.
|
||||
std::set<wxJoystick> Joysticks() const;
|
||||
|
||||
// Returns a copy of this object. This can be an expensive operation and
|
||||
// should only be used to modify the currently active shortcuts
|
||||
// configuration.
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <wx/translation.h>
|
||||
|
||||
#include "wx/strutils.h"
|
||||
#include "wx/wxutil.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
|
@ -202,26 +201,26 @@ UserInput StringToUserInput(const wxString& string) {
|
|||
if (kAxisRegex.Matches(remainder)) {
|
||||
kAxisRegex.GetMatch(&start, &length, 1);
|
||||
const int key = StringToInt(remainder.Mid(start, length));
|
||||
const int mod =
|
||||
remainder[start + length] == '+' ? wxJoyControl::AxisPlus : wxJoyControl::AxisMinus;
|
||||
return UserInput(key, mod, joy);
|
||||
const JoyControl control =
|
||||
remainder[start + length] == '+' ? JoyControl::AxisPlus : JoyControl::AxisMinus;
|
||||
return UserInput(key, control, JoyId(joy - 1));
|
||||
}
|
||||
if (kButtonRegex.Matches(remainder)) {
|
||||
kButtonRegex.GetMatch(&start, &length, 1);
|
||||
const int key = StringToInt(remainder.Mid(start, length));
|
||||
return UserInput(key, wxJoyControl::Button, joy);
|
||||
return UserInput(key, JoyControl::Button, JoyId(joy - 1));
|
||||
}
|
||||
if (kHatRegex.Matches(remainder)) {
|
||||
kHatRegex.GetMatch(&start, &length, 1);
|
||||
const int key = StringToInt(remainder.Mid(start, length));
|
||||
if (kHatRegex.GetMatch(remainder, 3).Length()) {
|
||||
return UserInput(key, wxJoyControl::HatNorth, joy);
|
||||
return UserInput(key, JoyControl::HatNorth, JoyId(joy - 1));
|
||||
} else if (kHatRegex.GetMatch(remainder, 4).Length()) {
|
||||
return UserInput(key, wxJoyControl::HatSouth, joy);
|
||||
return UserInput(key, JoyControl::HatSouth, JoyId(joy - 1));
|
||||
} else if (kHatRegex.GetMatch(remainder, 5).Length()) {
|
||||
return UserInput(key, wxJoyControl::HatEast, joy);
|
||||
return UserInput(key, JoyControl::HatEast, JoyId(joy - 1));
|
||||
} else if (kHatRegex.GetMatch(remainder, 6).Length()) {
|
||||
return UserInput(key, wxJoyControl::HatWest, joy);
|
||||
return UserInput(key, JoyControl::HatWest, JoyId(joy - 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,14 +266,47 @@ UserInput StringToUserInput(const wxString& string) {
|
|||
|
||||
} // namespace
|
||||
|
||||
UserInput::UserInput(const wxKeyEvent& event)
|
||||
: UserInput(Device::Keyboard, event.GetModifiers(), getKeyboardKeyCode(event), 0) {}
|
||||
// static
|
||||
JoyId JoyId::Invalid() {
|
||||
static constexpr int kInvalidSdlIndex = -1;
|
||||
return JoyId(kInvalidSdlIndex);
|
||||
}
|
||||
|
||||
UserInput::UserInput(const wxJoyEvent& event)
|
||||
wxString JoyId::ToString() {
|
||||
return wxString::Format("Joy%d", sdl_index_ + 1);
|
||||
}
|
||||
|
||||
bool JoyId::operator==(const JoyId& other) const {
|
||||
return sdl_index_ == other.sdl_index_;
|
||||
}
|
||||
bool JoyId::operator!=(const JoyId& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool JoyId::operator<(const JoyId& other) const {
|
||||
return sdl_index_ < other.sdl_index_;
|
||||
}
|
||||
bool JoyId::operator<=(const JoyId& other) const {
|
||||
return !(*this > other);
|
||||
}
|
||||
bool JoyId::operator>(const JoyId& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
bool JoyId::operator>=(const JoyId& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
JoyId::JoyId(int sdl_index) : sdl_index_(sdl_index) {}
|
||||
|
||||
UserInput::UserInput(uint8_t control_index, JoyControl control, JoyId joystick)
|
||||
: UserInput(Device::Joystick,
|
||||
event.control(),
|
||||
event.control_index(),
|
||||
event.joystick().player_index()) {}
|
||||
static_cast<int>(control),
|
||||
control_index,
|
||||
joystick.sdl_index_ + 1) {}
|
||||
|
||||
UserInput::UserInput(wxKeyCode key, wxKeyModifier mod)
|
||||
: UserInput(Device::Keyboard, mod, key, 0) {}
|
||||
|
||||
UserInput::UserInput(char c, wxKeyModifier mod) : UserInput(Device::Keyboard, mod, c, 0) {}
|
||||
|
||||
// static
|
||||
std::set<UserInput> UserInput::FromConfigString(const wxString& string) {
|
||||
|
@ -315,26 +347,26 @@ wxString UserInput::ToConfigString() const {
|
|||
return KeyboardInputToConfigString(mod_, key_);
|
||||
case Device::Joystick:
|
||||
wxString key;
|
||||
switch (mod_) {
|
||||
case wxJoyControl::AxisPlus:
|
||||
switch (static_cast<JoyControl>(mod_)) {
|
||||
case JoyControl::AxisPlus:
|
||||
key = wxString::Format(("Axis%d+"), key_);
|
||||
break;
|
||||
case wxJoyControl::AxisMinus:
|
||||
case JoyControl::AxisMinus:
|
||||
key = wxString::Format(("Axis%d-"), key_);
|
||||
break;
|
||||
case wxJoyControl::Button:
|
||||
case JoyControl::Button:
|
||||
key = wxString::Format(("Button%d"), key_);
|
||||
break;
|
||||
case wxJoyControl::HatNorth:
|
||||
case JoyControl::HatNorth:
|
||||
key = wxString::Format(("Hat%dN"), key_);
|
||||
break;
|
||||
case wxJoyControl::HatSouth:
|
||||
case JoyControl::HatSouth:
|
||||
key = wxString::Format(("Hat%dS"), key_);
|
||||
break;
|
||||
case wxJoyControl::HatWest:
|
||||
case JoyControl::HatWest:
|
||||
key = wxString::Format(("Hat%dW"), key_);
|
||||
break;
|
||||
case wxJoyControl::HatEast:
|
||||
case JoyControl::HatEast:
|
||||
key = wxString::Format(("Hat%dE"), key_);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,56 @@
|
|||
#ifndef VBAM_WX_CONFIG_USER_INPUT_H_
|
||||
#define VBAM_WX_CONFIG_USER_INPUT_H_
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/string.h>
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
|
||||
#include "wx/widgets/sdljoy.h"
|
||||
#include <wx/event.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace config {
|
||||
|
||||
// Forward declaration.
|
||||
class UserInput;
|
||||
|
||||
// One of the possible joystick controls.
|
||||
enum class JoyControl {
|
||||
AxisPlus = 0,
|
||||
AxisMinus,
|
||||
Button,
|
||||
HatNorth,
|
||||
HatSouth,
|
||||
HatWest,
|
||||
HatEast,
|
||||
Last = HatEast
|
||||
};
|
||||
|
||||
// Abstraction for a single joystick. In the current implementation, this
|
||||
// encapsulates an `sdl_index_`.
|
||||
class JoyId {
|
||||
public:
|
||||
static JoyId Invalid();
|
||||
|
||||
explicit JoyId(int sdl_index);
|
||||
virtual ~JoyId() = default;
|
||||
|
||||
wxString ToString();
|
||||
|
||||
bool operator==(const JoyId& other) const;
|
||||
bool operator!=(const JoyId& other) const;
|
||||
bool operator<(const JoyId& other) const;
|
||||
bool operator<=(const JoyId& other) const;
|
||||
bool operator>(const JoyId& other) const;
|
||||
bool operator>=(const JoyId& other) const;
|
||||
|
||||
private:
|
||||
JoyId() = delete;
|
||||
|
||||
int sdl_index_;
|
||||
|
||||
friend class UserInput;
|
||||
};
|
||||
|
||||
// Abstraction for a user input, which can come from a keyboard or a joystick.
|
||||
// This class implements comparison operators so it can be used in sets and as
|
||||
// a key in maps.
|
||||
|
@ -34,14 +76,15 @@ public:
|
|||
// Invalid UserInput, mainly used for comparison.
|
||||
UserInput() : UserInput(Device::Invalid, 0, 0, 0) {}
|
||||
|
||||
// Constructor from a wxKeyEvent.
|
||||
UserInput(const wxKeyEvent& event);
|
||||
// Constructor for a joystick input.
|
||||
UserInput(uint8_t control_index, JoyControl control, JoyId joystick);
|
||||
|
||||
// Constructor from a wxJoyEvent.
|
||||
UserInput(const wxJoyEvent& event);
|
||||
// Constructors for a keyboard input.
|
||||
UserInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE);
|
||||
UserInput(char c, wxKeyModifier mod = wxMOD_NONE);
|
||||
|
||||
// TODO: Remove this once all uses have been removed.
|
||||
UserInput(int key, int mod = 0, int joy = 0)
|
||||
explicit UserInput(int key, int mod = 0, int joy = 0)
|
||||
: UserInput(joy == 0 ? Device::Keyboard : Device::Joystick,
|
||||
mod,
|
||||
key,
|
||||
|
@ -55,14 +98,27 @@ public:
|
|||
// Converts to a localized string for display.
|
||||
wxString ToLocalizedString() const;
|
||||
|
||||
wxJoystick joystick() const { return joystick_; }
|
||||
JoyId joystick() const { return joystick_; }
|
||||
constexpr bool is_valid() const { return device_ != Device::Invalid; }
|
||||
constexpr operator bool() const { return is_valid(); }
|
||||
|
||||
bool is_joystick() const { return device_ == Device::Joystick; }
|
||||
bool is_keyboard() const { return device_ == Device::Keyboard; }
|
||||
|
||||
int key() const { return key_; }
|
||||
int mod() const { return mod_; }
|
||||
unsigned joy() const { return joy_; }
|
||||
|
||||
JoyControl joy_control() const {
|
||||
assert(is_joystick());
|
||||
return static_cast<JoyControl>(mod_);
|
||||
}
|
||||
|
||||
wxKeyCode key_code() const {
|
||||
assert(is_keyboard());
|
||||
return static_cast<wxKeyCode>(key_);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const UserInput& other) const {
|
||||
return device_ == other.device_ && mod_ == other.mod_ &&
|
||||
key_ == other.key_ && joy_ == other.joy_;
|
||||
|
@ -98,14 +154,14 @@ public:
|
|||
private:
|
||||
UserInput(Device device, int mod, int key, unsigned joy)
|
||||
: device_(device),
|
||||
joystick_(joy == 0 ? wxJoystick::Invalid()
|
||||
: wxJoystick::FromLegacyPlayerIndex(joy)),
|
||||
joystick_(joy == 0 ? JoyId::Invalid()
|
||||
: JoyId(joy - 1)),
|
||||
mod_(mod),
|
||||
key_(key),
|
||||
joy_(joy) {}
|
||||
|
||||
Device device_;
|
||||
wxJoystick joystick_;
|
||||
JoyId joystick_;
|
||||
int mod_;
|
||||
int key_;
|
||||
unsigned joy_;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#include "wx/dialogs/joypad-config.h"
|
||||
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/dialogs/base-dialog.h"
|
||||
#include "wx/opts.h"
|
||||
#include "wx/widgets/option-validator.h"
|
||||
#include "wx/widgets/user-input-ctrl.h"
|
||||
#include "wx/widgets/utils.h"
|
||||
#include "wx/wxvbam.h"
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
|
@ -87,7 +87,6 @@ void JoypadConfig::ToggleSDLGameControllerMode() {
|
|||
OPTION(kSDLGameControllerMode) =
|
||||
GetValidatedChild<wxCheckBox>("SDLGameControllerMode")->IsChecked();
|
||||
ClearAllJoypads();
|
||||
wxGetApp().frame->PollAllJoysticks();
|
||||
}
|
||||
|
||||
void JoypadConfig::ClearAllJoypads() {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_
|
||||
|
||||
#include "wx/dialogs/base-dialog.h"
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
// Manages the Joypad configuration dialog.
|
||||
|
|
101
src/wx/opts.cpp
101
src/wx/opts.cpp
|
@ -105,62 +105,70 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg,
|
|||
|
||||
} // namespace
|
||||
|
||||
#define WJKB config::UserInput
|
||||
|
||||
opts_t gopts;
|
||||
|
||||
const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBindings = {
|
||||
{ config::GameControl(0, config::GameKey::Up), {
|
||||
WJKB('W'),
|
||||
WJKB(11, wxJoyControl::Button, 1),
|
||||
WJKB(1, wxJoyControl::AxisMinus, 1),
|
||||
WJKB(3, wxJoyControl::AxisMinus, 1),
|
||||
{config::GameControl(0, config::GameKey::Up),
|
||||
{
|
||||
config::UserInput('W'),
|
||||
config::UserInput(11, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(1, config::JoyControl::AxisMinus, config::JoyId(0)),
|
||||
config::UserInput(3, config::JoyControl::AxisMinus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::Down), {
|
||||
WJKB('S'),
|
||||
WJKB(12, wxJoyControl::Button, 1),
|
||||
WJKB(1, wxJoyControl::AxisPlus, 1),
|
||||
WJKB(3, wxJoyControl::AxisPlus, 1),
|
||||
{config::GameControl(0, config::GameKey::Down),
|
||||
{
|
||||
config::UserInput('S'),
|
||||
config::UserInput(12, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(1, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
config::UserInput(3, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::Left), {
|
||||
WJKB('A'),
|
||||
WJKB(13, wxJoyControl::Button, 1),
|
||||
WJKB(0, wxJoyControl::AxisMinus, 1),
|
||||
WJKB(2, wxJoyControl::AxisMinus, 1),
|
||||
{config::GameControl(0, config::GameKey::Left),
|
||||
{
|
||||
config::UserInput('A'),
|
||||
config::UserInput(13, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(0, config::JoyControl::AxisMinus, config::JoyId(0)),
|
||||
config::UserInput(2, config::JoyControl::AxisMinus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::Right), {
|
||||
WJKB('D'),
|
||||
WJKB(14, wxJoyControl::Button, 1),
|
||||
WJKB(0, wxJoyControl::AxisPlus, 1),
|
||||
WJKB(2, wxJoyControl::AxisPlus, 1),
|
||||
{config::GameControl(0, config::GameKey::Right),
|
||||
{
|
||||
config::UserInput('D'),
|
||||
config::UserInput(14, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(0, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
config::UserInput(2, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::A), {
|
||||
WJKB('L'),
|
||||
WJKB(0, wxJoyControl::Button, 1),
|
||||
{config::GameControl(0, config::GameKey::A),
|
||||
{
|
||||
config::UserInput('L'),
|
||||
config::UserInput(0, config::JoyControl::Button, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::B), {
|
||||
WJKB('K'),
|
||||
WJKB(1, wxJoyControl::Button, 1),
|
||||
{config::GameControl(0, config::GameKey::B),
|
||||
{
|
||||
config::UserInput('K'),
|
||||
config::UserInput(1, config::JoyControl::Button, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::L), {
|
||||
WJKB('I'),
|
||||
WJKB(2, wxJoyControl::Button, 1),
|
||||
WJKB(9, wxJoyControl::Button, 1),
|
||||
WJKB(4, wxJoyControl::AxisPlus, 1),
|
||||
{config::GameControl(0, config::GameKey::L),
|
||||
{
|
||||
config::UserInput('I'),
|
||||
config::UserInput(2, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(9, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(4, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::R), {
|
||||
WJKB('O'),
|
||||
WJKB(3, wxJoyControl::Button, 1),
|
||||
WJKB(10, wxJoyControl::Button, 1),
|
||||
WJKB(5, wxJoyControl::AxisPlus, 1),
|
||||
{config::GameControl(0, config::GameKey::R),
|
||||
{
|
||||
config::UserInput('O'),
|
||||
config::UserInput(3, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(10, config::JoyControl::Button, config::JoyId(0)),
|
||||
config::UserInput(5, config::JoyControl::AxisPlus, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::Select), {
|
||||
WJKB(WXK_BACK),
|
||||
WJKB(4, wxJoyControl::Button, 1),
|
||||
{config::GameControl(0, config::GameKey::Select),
|
||||
{
|
||||
config::UserInput(WXK_BACK),
|
||||
config::UserInput(4, config::JoyControl::Button, config::JoyId(0)),
|
||||
}},
|
||||
{ config::GameControl(0, config::GameKey::Start), {
|
||||
WJKB(WXK_RETURN),
|
||||
WJKB(6, wxJoyControl::Button, 1),
|
||||
{config::GameControl(0, config::GameKey::Start),
|
||||
{
|
||||
config::UserInput(WXK_RETURN),
|
||||
config::UserInput(6, config::JoyControl::Button, config::JoyId(0)),
|
||||
}},
|
||||
{config::GameControl(0, config::GameKey::MotionUp), {}},
|
||||
{config::GameControl(0, config::GameKey::MotionDown), {}},
|
||||
|
@ -170,8 +178,9 @@ const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBinding
|
|||
{config::GameControl(0, config::GameKey::MotionOut), {}},
|
||||
{config::GameControl(0, config::GameKey::AutoA), {}},
|
||||
{config::GameControl(0, config::GameKey::AutoB), {}},
|
||||
{ config::GameControl(0, config::GameKey::Speed), {
|
||||
WJKB(WXK_SPACE),
|
||||
{config::GameControl(0, config::GameKey::Speed),
|
||||
{
|
||||
config::UserInput(WXK_SPACE),
|
||||
}},
|
||||
{config::GameControl(0, config::GameKey::Capture), {}},
|
||||
{config::GameControl(0, config::GameKey::Gameshark), {}},
|
||||
|
|
157
src/wx/panel.cpp
157
src/wx/panel.cpp
|
@ -1,10 +1,8 @@
|
|||
#include "wx/wxvbam.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include "components/filters_agb/filters_agb.h"
|
||||
#include "components/filters_interframe/interframe.h"
|
||||
#include "core/base/system.h"
|
||||
#include "wx/config/option-id.h"
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <X11/Xlib.h>
|
||||
|
@ -22,13 +20,14 @@
|
|||
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/menu.h>
|
||||
#include <SDL_joystick.h>
|
||||
|
||||
#include "background-input.h"
|
||||
#include "components/draw_text/draw_text.h"
|
||||
#include "components/filters/filters.h"
|
||||
#include "components/filters_agb/filters_agb.h"
|
||||
#include "components/filters_interframe/interframe.h"
|
||||
#include "core/base/file_util.h"
|
||||
#include "core/base/patch.h"
|
||||
#include "core/base/system.h"
|
||||
#include "core/base/version.h"
|
||||
#include "core/gb/gb.h"
|
||||
#include "core/gb/gbCheats.h"
|
||||
|
@ -41,15 +40,15 @@
|
|||
#include "core/gba/gbaPrint.h"
|
||||
#include "core/gba/gbaRtc.h"
|
||||
#include "core/gba/gbaSound.h"
|
||||
#include "wx/background-input.h"
|
||||
#include "wx/config/game-control.h"
|
||||
#include "wx/config/option-id.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/option.h"
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/drawing.h"
|
||||
#include "wx/wayland.h"
|
||||
#include "wx/widgets/render-plugin.h"
|
||||
#include "wx/wxutil.h"
|
||||
#include "wx/wxvbam.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <windows.h>
|
||||
|
@ -165,6 +164,7 @@ 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
|
||||
|
@ -440,8 +440,6 @@ void GameArea::LoadGame(const wxString& name)
|
|||
was_paused = true;
|
||||
schedule_audio_restart_ = false;
|
||||
MainFrame* mf = wxGetApp().frame;
|
||||
mf->StopJoyPollTimer();
|
||||
mf->SetJoystick();
|
||||
mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA);
|
||||
mf->cmd_enable |= ONLOAD_CMDEN;
|
||||
mf->cmd_enable |= loaded == IMAGE_GB ? CMDEN_GB : (CMDEN_GBA | CMDEN_NGDB_GBA);
|
||||
|
@ -697,8 +695,6 @@ void GameArea::UnloadGame(bool destruct)
|
|||
mf->cmd_enable &= UNLOAD_CMDEN_KEEP;
|
||||
mf->update_state_ts(true);
|
||||
mf->enable_menus();
|
||||
mf->StartJoyPollTimer();
|
||||
mf->SetJoystick();
|
||||
mf->ResetCheatSearch();
|
||||
|
||||
if (rewind_mem)
|
||||
|
@ -1087,8 +1083,6 @@ void GameArea::Pause()
|
|||
|
||||
if (loaded != IMAGE_UNKNOWN)
|
||||
soundPause();
|
||||
|
||||
wxGetApp().frame->StartJoyPollTimer();
|
||||
}
|
||||
|
||||
void GameArea::Resume()
|
||||
|
@ -1103,8 +1097,6 @@ void GameArea::Resume()
|
|||
if (loaded != IMAGE_UNKNOWN)
|
||||
soundResume();
|
||||
|
||||
wxGetApp().frame->StopJoyPollTimer();
|
||||
|
||||
SetFocus();
|
||||
}
|
||||
|
||||
|
@ -1184,31 +1176,32 @@ void GameArea::OnIdle(wxIdleEvent& event)
|
|||
wxWindow* w = panel->GetWindow();
|
||||
|
||||
// set up event handlers
|
||||
w->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this);
|
||||
w->Connect(wxEVT_KEY_UP, wxKeyEventHandler(GameArea::OnKeyUp), NULL, this);
|
||||
w->Connect(wxEVT_PAINT, wxPaintEventHandler(GameArea::PaintEv), NULL, this);
|
||||
w->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(GameArea::EraseBackground), NULL, this);
|
||||
w->Bind(VBAM_EVT_USER_INPUT_DOWN, &GameArea::OnUserInputDown, this);
|
||||
w->Bind(VBAM_EVT_USER_INPUT_UP, &GameArea::OnUserInputUp, this);
|
||||
w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this);
|
||||
w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this);
|
||||
|
||||
// set userdata so we know it's the panel and not the frame being resized
|
||||
// the userdata is freed on disconnect/destruction
|
||||
this->Connect(wxEVT_SIZE, wxSizeEventHandler(GameArea::OnSize), NULL, this);
|
||||
this->Bind(wxEVT_SIZE, &GameArea::OnSize, this);
|
||||
|
||||
// We need to check if the buttons stayed pressed when focus the panel.
|
||||
w->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(GameArea::OnKillFocus), NULL, this);
|
||||
w->Bind(wxEVT_KILL_FOCUS, &GameArea::OnKillFocus, this);
|
||||
|
||||
// Update mouse last-used timers on mouse events etc..
|
||||
w->Connect(wxEVT_MOTION, wxMouseEventHandler(GameArea::MouseEvent), NULL, this);
|
||||
w->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this);
|
||||
w->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this);
|
||||
w->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this);
|
||||
w->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(GameArea::MouseEvent), NULL, this);
|
||||
w->Bind(wxEVT_MOTION, &GameArea::MouseEvent, this);
|
||||
w->Bind(wxEVT_LEFT_DOWN, &GameArea::MouseEvent, this);
|
||||
w->Bind(wxEVT_RIGHT_DOWN, &GameArea::MouseEvent, this);
|
||||
w->Bind(wxEVT_MIDDLE_DOWN, &GameArea::MouseEvent, this);
|
||||
w->Bind(wxEVT_MOUSEWHEEL, &GameArea::MouseEvent, this);
|
||||
|
||||
w->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
|
||||
w->SetSize(wxSize(basic_width, basic_height));
|
||||
|
||||
// Allow input while on background
|
||||
if (OPTION(kUIAllowKeyboardBackgroundInput))
|
||||
enableKeyboardBackgroundInput(w);
|
||||
if (OPTION(kUIAllowKeyboardBackgroundInput)) {
|
||||
enableKeyboardBackgroundInput(w->GetEventHandler());
|
||||
}
|
||||
|
||||
if (gopts.max_scale)
|
||||
w->SetMaxSize(wxSize(basic_width * gopts.max_scale,
|
||||
|
@ -1251,8 +1244,6 @@ void GameArea::OnIdle(wxIdleEvent& event)
|
|||
UpdateLcdFilter();
|
||||
}
|
||||
|
||||
mf->PollJoysticks();
|
||||
|
||||
if (!paused) {
|
||||
HidePointer();
|
||||
HideMenuBar();
|
||||
|
@ -1323,14 +1314,6 @@ void GameArea::OnIdle(wxIdleEvent& event)
|
|||
}
|
||||
}
|
||||
|
||||
static bool process_user_input(bool down, const config::UserInput& user_input)
|
||||
{
|
||||
if (down)
|
||||
return config::GameControlState::Instance().OnInputPressed(user_input);
|
||||
else
|
||||
return config::GameControlState::Instance().OnInputReleased(user_input);
|
||||
}
|
||||
|
||||
static void draw_black_background(wxWindow* win) {
|
||||
wxClientDC dc(win);
|
||||
wxCoord w, h;
|
||||
|
@ -1340,70 +1323,32 @@ static void draw_black_background(wxWindow* win) {
|
|||
dc.DrawRectangle(0, 0, w, h);
|
||||
}
|
||||
|
||||
static void process_keyboard_event(const wxKeyEvent& ev, bool down)
|
||||
{
|
||||
int kc = ev.GetKeyCode();
|
||||
|
||||
// Under Wayland or if the key is unicode, we can't use wxGetKeyState().
|
||||
if (!IsWayland() && kc != WXK_NONE) {
|
||||
// Check if the key state corresponds to the event.
|
||||
if (down != wxGetKeyState(static_cast<wxKeyCode>(kc))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int key = getKeyboardKeyCode(ev);
|
||||
int mod = ev.GetModifiers();
|
||||
|
||||
if (key == WXK_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// modifier-only key releases do not set the modifier flag
|
||||
// so we set it here to match key release events to key press events
|
||||
switch (key) {
|
||||
case WXK_SHIFT:
|
||||
mod |= wxMOD_SHIFT;
|
||||
break;
|
||||
case WXK_ALT:
|
||||
mod |= wxMOD_ALT;
|
||||
break;
|
||||
case WXK_CONTROL:
|
||||
mod |= wxMOD_CONTROL;
|
||||
break;
|
||||
#ifdef __WXMAC__
|
||||
case WXK_RAW_CONTROL:
|
||||
mod |= wxMOD_RAW_CONTROL;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
// This resets mod for any non-modifier key. This has the side
|
||||
// effect of not being able to set up a modifier+key for a game
|
||||
// control.
|
||||
mod = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (process_user_input(down, config::UserInput(key, mod, 0))) {
|
||||
wxWakeUpIdle();
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef __WXGTK__
|
||||
static Display* GetX11Display()
|
||||
{
|
||||
static Display* GetX11Display() {
|
||||
return GDK_WINDOW_XDISPLAY(gtk_widget_get_window(wxGetApp().frame->GetHandle()));
|
||||
}
|
||||
#endif // __WXGTK__
|
||||
|
||||
void GameArea::OnKeyDown(wxKeyEvent& ev)
|
||||
{
|
||||
process_keyboard_event(ev, true);
|
||||
void GameArea::OnUserInputDown(widgets::UserInputEvent& event) {
|
||||
if (config::GameControlState::Instance().OnInputPressed(event.input())) {
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
}
|
||||
|
||||
void GameArea::OnKeyUp(wxKeyEvent& ev)
|
||||
{
|
||||
process_keyboard_event(ev, false);
|
||||
void GameArea::OnUserInputUp(widgets::UserInputEvent& event) {
|
||||
if (config::GameControlState::Instance().OnInputReleased(event.input())) {
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
// tell Linux to turn off the screensaver/screen-blank if joystick button was pressed
|
||||
// this shouldn't be necessary of course
|
||||
#if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS)
|
||||
if (event.input().is_joystick() && !wxGetApp().UsingWayland()) {
|
||||
auto display = GetX11Display();
|
||||
XResetScreenSaver(display);
|
||||
XFlush(display);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// these three are forwarded to the DrawingPanel instance
|
||||
|
@ -1430,24 +1375,8 @@ void GameArea::OnSize(wxSizeEvent& ev)
|
|||
ev.Skip();
|
||||
}
|
||||
|
||||
void GameArea::OnSDLJoy(wxJoyEvent& ev)
|
||||
{
|
||||
process_user_input(ev.pressed(), config::UserInput(ev));
|
||||
|
||||
// tell Linux to turn off the screensaver/screen-blank if joystick button was pressed
|
||||
// this shouldn't be necessary of course
|
||||
#if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS)
|
||||
if (!wxGetApp().UsingWayland()) {
|
||||
auto display = GetX11Display();
|
||||
XResetScreenSaver(display);
|
||||
XFlush(display);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(GameArea, wxPanel)
|
||||
EVT_IDLE(GameArea::OnIdle)
|
||||
EVT_SDLJOY(GameArea::OnSDLJoy)
|
||||
// FIXME: wxGTK does not generate motion events in MainFrame (not sure
|
||||
// what to do about it)
|
||||
EVT_MOUSE_EVENTS(GameArea::MouseEvent)
|
||||
|
@ -1525,6 +1454,7 @@ DrawingPanel::DrawingPanel(wxWindow* parent, int _width, int _height)
|
|||
, wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(),
|
||||
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
|
||||
{
|
||||
this->SetClientData(new widgets::UserInputEventSender(this));
|
||||
}
|
||||
|
||||
void DrawingPanelBase::DrawingPanelInit()
|
||||
|
@ -2256,6 +2186,7 @@ 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->SetClientData(new widgets::UserInputEventSender(this));
|
||||
widgets::RequestHighResolutionOpenGlSurfaceForWindow(this);
|
||||
SetContext();
|
||||
}
|
||||
|
|
|
@ -596,7 +596,7 @@ uint32_t systemGetClock()
|
|||
|
||||
void systemCartridgeRumble(bool b)
|
||||
{
|
||||
wxGetApp().frame->SetJoystickRumble(b);
|
||||
wxGetApp().sdl_poller()->SetRumble(b);
|
||||
}
|
||||
|
||||
static uint8_t sensorDarkness = 0xE8; // total darkness (including daylight on rainy days)
|
||||
|
|
|
@ -1,10 +1,38 @@
|
|||
#include "wx/viewsupt.h"
|
||||
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/wxutil.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
|
||||
|
|
|
@ -0,0 +1,439 @@
|
|||
#include "wx/widgets/sdl-poller.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/toplevel.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "wx/config/option-id.h"
|
||||
#include "wx/config/option-observer.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/option.h"
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class JoyAxisStatus { Neutral = 0, Plus, Minus };
|
||||
|
||||
JoyAxisStatus AxisValueToStatus(const int16_t& x) {
|
||||
if (x > 0x1fff)
|
||||
return JoyAxisStatus::Plus;
|
||||
if (x < -0x1fff)
|
||||
return JoyAxisStatus::Minus;
|
||||
return JoyAxisStatus::Neutral;
|
||||
}
|
||||
|
||||
config::JoyControl AxisStatusToJoyControl(const JoyAxisStatus& status) {
|
||||
switch (status) {
|
||||
case JoyAxisStatus::Plus:
|
||||
return config::JoyControl::AxisPlus;
|
||||
case JoyAxisStatus::Minus:
|
||||
return config::JoyControl::AxisMinus;
|
||||
case JoyAxisStatus::Neutral:
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
return config::JoyControl::AxisPlus;
|
||||
}
|
||||
}
|
||||
|
||||
config::JoyControl HatStatusToJoyControl(const uint8_t status) {
|
||||
switch (status) {
|
||||
case SDL_HAT_UP:
|
||||
return config::JoyControl::HatNorth;
|
||||
case SDL_HAT_DOWN:
|
||||
return config::JoyControl::HatSouth;
|
||||
case SDL_HAT_LEFT:
|
||||
return config::JoyControl::HatWest;
|
||||
case SDL_HAT_RIGHT:
|
||||
return config::JoyControl::HatEast;
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
return config::JoyControl::HatNorth;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Represents the current state of a joystick. This class takes care of
|
||||
// initializing and destroying SDL resources on construction and destruction so
|
||||
// every associated SDL state for a joystick dies with this object.
|
||||
class JoyState : public wxTimer {
|
||||
public:
|
||||
JoyState(bool enable_game_controller, int sdl_index);
|
||||
~JoyState() override;
|
||||
|
||||
// Move constructor and assignment.
|
||||
JoyState(JoyState&& other);
|
||||
JoyState& operator=(JoyState&& other);
|
||||
|
||||
// Disable copy constructor and assignment.
|
||||
JoyState(const JoyState&) = delete;
|
||||
JoyState& operator=(const JoyState&) = delete;
|
||||
|
||||
// Returns true if this object was properly initialized.
|
||||
bool IsValid() const;
|
||||
|
||||
// Returns true if this object is a game controller.
|
||||
bool is_game_controller() const { return !!game_controller_; }
|
||||
|
||||
// Processes the corresponding events.
|
||||
std::vector<UserInputEvent> ProcessAxisEvent(const uint8_t index, const JoyAxisStatus status);
|
||||
std::vector<UserInputEvent> ProcessButtonEvent(const uint8_t index, const bool pressed);
|
||||
std::vector<UserInputEvent> ProcessHatEvent(const uint8_t index, const uint8_t status);
|
||||
|
||||
// Activates or deactivates rumble.
|
||||
void SetRumble(bool activate_rumble);
|
||||
|
||||
SDL_JoystickID joystick_id() const { return joystick_id_; }
|
||||
|
||||
private:
|
||||
// wxTimer implementation.
|
||||
// Used to rumble on a timer.
|
||||
void Notify() override;
|
||||
|
||||
// The Joystick abstraction for UI events.
|
||||
config::JoyId wx_joystick_;
|
||||
|
||||
// SDL Joystick ID used for events.
|
||||
SDL_JoystickID joystick_id_;
|
||||
|
||||
// The SDL GameController instance.
|
||||
SDL_GameController* game_controller_ = nullptr;
|
||||
|
||||
// The SDL Joystick instance.
|
||||
SDL_Joystick* sdl_joystick_ = nullptr;
|
||||
|
||||
// Current state of Joystick axis.
|
||||
std::unordered_map<uint8_t, JoyAxisStatus> axis_{};
|
||||
|
||||
// Current state of Joystick buttons.
|
||||
std::unordered_map<uint8_t, bool> buttons_{};
|
||||
|
||||
// Current state of Joystick HAT. Unused for GameControllers.
|
||||
std::unordered_map<uint8_t, uint8_t> hats_{};
|
||||
|
||||
// Set to true to activate joystick rumble.
|
||||
bool rumbling_ = false;
|
||||
};
|
||||
|
||||
JoyState::JoyState(bool enable_game_controller, int sdl_index) : wx_joystick_(sdl_index) {
|
||||
if (enable_game_controller && SDL_IsGameController(sdl_index)) {
|
||||
game_controller_ = SDL_GameControllerOpen(sdl_index);
|
||||
if (game_controller_)
|
||||
sdl_joystick_ = SDL_GameControllerGetJoystick(game_controller_);
|
||||
} else {
|
||||
sdl_joystick_ = SDL_JoystickOpen(sdl_index);
|
||||
}
|
||||
|
||||
if (!sdl_joystick_)
|
||||
return;
|
||||
|
||||
joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_);
|
||||
}
|
||||
|
||||
JoyState::~JoyState() {
|
||||
// Nothing to do if this object is not initialized.
|
||||
if (!sdl_joystick_)
|
||||
return;
|
||||
|
||||
if (game_controller_) {
|
||||
SDL_GameControllerClose(game_controller_);
|
||||
} else {
|
||||
SDL_JoystickClose(sdl_joystick_);
|
||||
}
|
||||
}
|
||||
|
||||
JoyState::JoyState(JoyState&& other) : wx_joystick_(other.wx_joystick_) {
|
||||
if (this == &other) {
|
||||
return;
|
||||
}
|
||||
|
||||
sdl_joystick_ = other.sdl_joystick_;
|
||||
game_controller_ = other.game_controller_;
|
||||
joystick_id_ = other.joystick_id_;
|
||||
axis_ = std::move(other.axis_);
|
||||
buttons_ = std::move(other.buttons_);
|
||||
hats_ = std::move(other.hats_);
|
||||
rumbling_ = other.rumbling_;
|
||||
|
||||
other.sdl_joystick_ = nullptr;
|
||||
other.game_controller_ = nullptr;
|
||||
}
|
||||
|
||||
JoyState& JoyState::operator=(JoyState&& other) {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
sdl_joystick_ = other.sdl_joystick_;
|
||||
game_controller_ = other.game_controller_;
|
||||
joystick_id_ = other.joystick_id_;
|
||||
axis_ = std::move(other.axis_);
|
||||
buttons_ = std::move(other.buttons_);
|
||||
hats_ = std::move(other.hats_);
|
||||
rumbling_ = other.rumbling_;
|
||||
|
||||
other.sdl_joystick_ = nullptr;
|
||||
other.game_controller_ = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool JoyState::IsValid() const {
|
||||
return sdl_joystick_;
|
||||
}
|
||||
|
||||
std::vector<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index,
|
||||
const JoyAxisStatus status) {
|
||||
const JoyAxisStatus previous_status = axis_[index];
|
||||
std::vector<UserInputEvent> events;
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return events;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
axis_[index] = status;
|
||||
|
||||
if (previous_status != JoyAxisStatus::Neutral) {
|
||||
// Send the "unpressed" event.
|
||||
events.push_back(UserInputEvent(
|
||||
config::UserInput(index, AxisStatusToJoyControl(previous_status), wx_joystick_),
|
||||
false));
|
||||
}
|
||||
|
||||
// We already sent the "unpressed" event so nothing more to do.
|
||||
if (status == JoyAxisStatus::Neutral) {
|
||||
return events;
|
||||
}
|
||||
|
||||
// Send the "pressed" event.
|
||||
events.push_back(UserInputEvent(
|
||||
config::UserInput(index, AxisStatusToJoyControl(status), wx_joystick_), true));
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
std::vector<UserInputEvent> JoyState::ProcessButtonEvent(const uint8_t index, const bool status) {
|
||||
const bool previous_status = buttons_[index];
|
||||
std::vector<UserInputEvent> events;
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return events;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
buttons_[index] = status;
|
||||
|
||||
// Send the event.
|
||||
events.push_back(
|
||||
UserInputEvent(config::UserInput(index, config::JoyControl::Button, wx_joystick_), status));
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
std::vector<UserInputEvent> JoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) {
|
||||
const uint16_t previous_status = hats_[index];
|
||||
std::vector<UserInputEvent> events;
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return events;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
hats_[index] = status;
|
||||
|
||||
// For HATs, the status value is a bit field, where each bit corresponds to
|
||||
// a direction. These are parsed here to send the corresponding "pressed"
|
||||
// and "unpressed" events.
|
||||
for (uint8_t bit = 0x01; bit != 0x10; bit <<= 1) {
|
||||
const bool old_control_pressed = (previous_status & bit) != 0;
|
||||
const bool new_control_pressed = (status & bit) != 0;
|
||||
if (old_control_pressed && !new_control_pressed) {
|
||||
// Send the "unpressed" event.
|
||||
events.push_back(UserInputEvent(
|
||||
config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), false));
|
||||
}
|
||||
if (!old_control_pressed && new_control_pressed) {
|
||||
// Send the "pressed" event.
|
||||
events.push_back(UserInputEvent(
|
||||
config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), true));
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
void JoyState::SetRumble(bool activate_rumble) {
|
||||
rumbling_ = activate_rumble;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (!game_controller_)
|
||||
return;
|
||||
|
||||
if (rumbling_) {
|
||||
SDL_GameControllerRumble(game_controller_, 0xFFFF, 0xFFFF, 300);
|
||||
if (!IsRunning()) {
|
||||
Start(150);
|
||||
}
|
||||
} else {
|
||||
SDL_GameControllerRumble(game_controller_, 0, 0, 0);
|
||||
Stop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void JoyState::Notify() {
|
||||
SetRumble(rumbling_);
|
||||
}
|
||||
|
||||
SdlPoller::SdlPoller(const EventHandlerProvider 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()); }) {
|
||||
wxTimer::Start(50);
|
||||
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
|
||||
SDL_GameControllerEventState(SDL_ENABLE);
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
}
|
||||
|
||||
SdlPoller::~SdlPoller() {
|
||||
wxTimer::Stop();
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void SdlPoller::SetRumble(bool rumble) {
|
||||
if (rumble) {
|
||||
if (!joystick_states_.empty()) {
|
||||
auto it = joystick_states_.begin();
|
||||
it->second.SetRumble(true);
|
||||
}
|
||||
} else {
|
||||
if (!joystick_states_.empty()) {
|
||||
auto it = joystick_states_.begin();
|
||||
it->second.SetRumble(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SdlPoller::ReconnectControllers(bool enable_game_controller) {
|
||||
enable_game_controller_ = enable_game_controller;
|
||||
RemapControllers();
|
||||
}
|
||||
|
||||
void SdlPoller::Notify() {
|
||||
SDL_Event sdl_event;
|
||||
|
||||
while (SDL_PollEvent(&sdl_event)) {
|
||||
std::vector<UserInputEvent> events;
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Do nothing. This will be handled with JOYDEVICEADDED and
|
||||
// JOYDEVICEREMOVED events.
|
||||
break;
|
||||
|
||||
// Joystick events for non-GameControllers.
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEADDED:
|
||||
// Always remap all controllers.
|
||||
RemapControllers();
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
joystick_states_.erase(sdl_event.jdevice.which);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!events.empty()) {
|
||||
wxEvtHandler* handler = handler_provider_();
|
||||
if (handler) {
|
||||
for (const auto& user_input_event : events) {
|
||||
handler->QueueEvent(user_input_event.Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JoyState* SdlPoller::FindJoyState(const SDL_JoystickID& joy_id) {
|
||||
auto it = joystick_states_.find(joy_id);
|
||||
if (it == joystick_states_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
void SdlPoller::RemapControllers() {
|
||||
// Clear the current joystick states.
|
||||
joystick_states_.clear();
|
||||
|
||||
// Reconnect all controllers.
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||
JoyState joy_state(enable_game_controller_, i);
|
||||
if (joy_state.IsValid()) {
|
||||
joystick_states_.insert({joy_state.joystick_id(), std::move(joy_state)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace widgets
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef WX_WIDGETS_SDL_POLLER_H_
|
||||
#define WX_WIDGETS_SDL_POLLER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "wx/config/option-observer.h"
|
||||
|
||||
// Forward declarations.
|
||||
class wxEvtHandler;
|
||||
|
||||
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);
|
||||
~SdlPoller() final;
|
||||
|
||||
// Disable copy and copy assignment.
|
||||
SdlPoller(const SdlPoller&) = delete;
|
||||
SdlPoller& operator=(const SdlPoller&) = delete;
|
||||
|
||||
// Sets or unsets the controller rumble.
|
||||
void SetRumble(bool rumble);
|
||||
|
||||
private:
|
||||
// Helper method to find a joystick state from a joystick ID.
|
||||
// Returns nullptr if not present. Called from the SDL worker thread.
|
||||
JoyState* FindJoyState(const SDL_JoystickID& joy_id);
|
||||
|
||||
// Remap all controllers. Called from the SDL worker thread.
|
||||
void RemapControllers();
|
||||
|
||||
// Reconnects all controllers.
|
||||
void ReconnectControllers(bool enable_game_controller);
|
||||
|
||||
// wxTimer implementation.
|
||||
void Notify() final;
|
||||
|
||||
// Map of SDL joystick ID to joystick state. Only contains active joysticks.
|
||||
// Only accessed from the SDL worker thread.
|
||||
std::map<SDL_JoystickID, JoyState> joystick_states_;
|
||||
|
||||
// Set to true to enable the game controller API.
|
||||
bool enable_game_controller_ = false;
|
||||
|
||||
// The provider of event handlers to send the events to.
|
||||
const EventHandlerProvider handler_provider_;
|
||||
|
||||
// Observer for the game controller enabled option.
|
||||
const config::OptionsObserver game_controller_enabled_observer_;
|
||||
};
|
||||
|
||||
} // namespace widgets
|
||||
|
||||
#endif // WX_WIDGETS_SDL_POLLER_H_
|
|
@ -1,493 +0,0 @@
|
|||
#include "wx/widgets/sdljoy.h"
|
||||
|
||||
#include <wx/timer.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/option.h"
|
||||
#include "wx/wxvbam.h"
|
||||
|
||||
namespace {
|
||||
|
||||
enum class wxAxisStatus {
|
||||
Neutral = 0,
|
||||
Plus,
|
||||
Minus
|
||||
};
|
||||
|
||||
wxAxisStatus AxisValueToStatus(const int16_t& x) {
|
||||
if (x > 0x1fff)
|
||||
return wxAxisStatus::Plus;
|
||||
if (x < -0x1fff)
|
||||
return wxAxisStatus::Minus;
|
||||
return wxAxisStatus::Neutral;
|
||||
}
|
||||
|
||||
wxJoyControl AxisStatusToJoyControl(const wxAxisStatus& status) {
|
||||
switch (status) {
|
||||
case wxAxisStatus::Plus:
|
||||
return wxJoyControl::AxisPlus;
|
||||
case wxAxisStatus::Minus:
|
||||
return wxJoyControl::AxisMinus;
|
||||
case wxAxisStatus::Neutral:
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
return wxJoyControl::AxisPlus;
|
||||
}
|
||||
}
|
||||
|
||||
wxJoyControl HatStatusToJoyControl(const uint8_t status) {
|
||||
switch (status) {
|
||||
case SDL_HAT_UP:
|
||||
return wxJoyControl::HatNorth;
|
||||
case SDL_HAT_DOWN:
|
||||
return wxJoyControl::HatSouth;
|
||||
case SDL_HAT_LEFT:
|
||||
return wxJoyControl::HatWest;
|
||||
case SDL_HAT_RIGHT:
|
||||
return wxJoyControl::HatEast;
|
||||
default:
|
||||
// This should never happen.
|
||||
assert(false);
|
||||
return wxJoyControl::HatNorth;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// For testing a GameController as a Joystick:
|
||||
//#define SDL_IsGameController(x) false
|
||||
|
||||
// static
|
||||
wxJoystick wxJoystick::Invalid() {
|
||||
return wxJoystick(kInvalidSdlIndex);
|
||||
}
|
||||
|
||||
// static
|
||||
wxJoystick wxJoystick::FromLegacyPlayerIndex(unsigned player_index) {
|
||||
assert(player_index != 0);
|
||||
return wxJoystick(player_index - 1);
|
||||
}
|
||||
|
||||
wxString wxJoystick::ToString() {
|
||||
return wxString::Format("Joy%d", sdl_index_ + 1);
|
||||
}
|
||||
|
||||
bool wxJoystick::operator==(const wxJoystick& other) const {
|
||||
return sdl_index_ == other.sdl_index_;
|
||||
}
|
||||
bool wxJoystick::operator!=(const wxJoystick& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool wxJoystick::operator<(const wxJoystick& other) const {
|
||||
return sdl_index_ < other.sdl_index_;
|
||||
}
|
||||
bool wxJoystick::operator<=(const wxJoystick& other) const {
|
||||
return !(*this > other);
|
||||
}
|
||||
bool wxJoystick::operator>(const wxJoystick& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
bool wxJoystick::operator>=(const wxJoystick& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
wxJoystick::wxJoystick(int sdl_index) : sdl_index_(sdl_index) {}
|
||||
|
||||
wxJoyEvent::wxJoyEvent(
|
||||
wxJoystick joystick,
|
||||
wxJoyControl control,
|
||||
uint8_t control_index,
|
||||
bool pressed) :
|
||||
wxCommandEvent(wxEVT_JOY),
|
||||
joystick_(joystick),
|
||||
control_(control),
|
||||
control_index_(control_index),
|
||||
pressed_(pressed) {}
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_JOY, wxJoyEvent);
|
||||
|
||||
// Represents the current state of a joystick. This class takes care of
|
||||
// initializing and destroying SDL resources on construction and destruction so
|
||||
// every associated SDL state for a joystick dies with this object.
|
||||
class wxSDLJoyState : public wxTimer {
|
||||
public:
|
||||
explicit wxSDLJoyState(int sdl_index);
|
||||
explicit wxSDLJoyState(wxJoystick joystick);
|
||||
~wxSDLJoyState() override;
|
||||
|
||||
// Disable copy constructor and assignment. This is to prevent double
|
||||
// closure of the SDL objects.
|
||||
wxSDLJoyState(const wxSDLJoyState&) = delete;
|
||||
wxSDLJoyState& operator=(const wxSDLJoyState&) = delete;
|
||||
|
||||
// Returns true if this object was properly initialized.
|
||||
bool IsValid() const;
|
||||
|
||||
// Returns true if this object is a game controller.
|
||||
bool is_game_controller() const { return !!game_controller_; }
|
||||
|
||||
// Processes the corresponding events.
|
||||
void ProcessAxisEvent(const uint8_t index, const wxAxisStatus status);
|
||||
void ProcessButtonEvent(const uint8_t index, const bool pressed);
|
||||
void ProcessHatEvent(const uint8_t index, const uint8_t status);
|
||||
|
||||
// Activates or deactivates rumble.
|
||||
void SetRumble(bool activate_rumble);
|
||||
|
||||
SDL_JoystickID joystick_id() const { return joystick_id_; }
|
||||
|
||||
private:
|
||||
// wxTimer implementation.
|
||||
// Used to rumble on a timer.
|
||||
void Notify() override;
|
||||
|
||||
// The Joystick abstraction for UI events.
|
||||
wxJoystick wx_joystick_;
|
||||
|
||||
// SDL Joystick ID used for events.
|
||||
SDL_JoystickID joystick_id_;
|
||||
|
||||
// The SDL GameController instance.
|
||||
SDL_GameController* game_controller_ = nullptr;
|
||||
|
||||
// The SDL Joystick instance.
|
||||
SDL_Joystick* sdl_joystick_ = nullptr;
|
||||
|
||||
// Current state of Joystick axis.
|
||||
std::unordered_map<uint8_t, wxAxisStatus> axis_{};
|
||||
|
||||
// Current state of Joystick buttons.
|
||||
std::unordered_map<uint8_t, bool> buttons_{};
|
||||
|
||||
// Current state of Joystick HAT. Unused for GameControllers.
|
||||
std::unordered_map<uint8_t, uint8_t> hats_{};
|
||||
|
||||
// Set to true to activate joystick rumble.
|
||||
bool rumbling_ = false;
|
||||
};
|
||||
|
||||
wxSDLJoyState::wxSDLJoyState(int sdl_index)
|
||||
: wxSDLJoyState(wxJoystick(sdl_index)) {}
|
||||
|
||||
wxSDLJoyState::wxSDLJoyState(wxJoystick joystick) : wx_joystick_(joystick) {
|
||||
int sdl_index = wx_joystick_.sdl_index_;
|
||||
if (OPTION(kSDLGameControllerMode) && SDL_IsGameController(sdl_index)) {
|
||||
game_controller_ = SDL_GameControllerOpen(sdl_index);
|
||||
if (game_controller_)
|
||||
sdl_joystick_ = SDL_GameControllerGetJoystick(game_controller_);
|
||||
} else {
|
||||
sdl_joystick_ = SDL_JoystickOpen(sdl_index);
|
||||
}
|
||||
|
||||
if (!sdl_joystick_)
|
||||
return;
|
||||
|
||||
joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_);
|
||||
systemScreenMessage(
|
||||
wxString::Format(_("Connected %s: %s"),
|
||||
wx_joystick_.ToString(), SDL_JoystickNameForIndex(sdl_index)));
|
||||
}
|
||||
|
||||
wxSDLJoyState::~wxSDLJoyState() {
|
||||
// Nothing to do if this object is not initialized.
|
||||
if (!sdl_joystick_)
|
||||
return;
|
||||
|
||||
if (game_controller_)
|
||||
SDL_GameControllerClose(game_controller_);
|
||||
else
|
||||
SDL_JoystickClose(sdl_joystick_);
|
||||
|
||||
systemScreenMessage(
|
||||
wxString::Format(_("Disconnected %s"), wx_joystick_.ToString()));
|
||||
}
|
||||
|
||||
bool wxSDLJoyState::IsValid() const {
|
||||
return sdl_joystick_;
|
||||
}
|
||||
|
||||
void wxSDLJoyState::ProcessAxisEvent(const uint8_t index, const wxAxisStatus status) {
|
||||
auto handler = wxGetApp().frame->GetJoyEventHandler();
|
||||
const wxAxisStatus previous_status = axis_[index];
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
axis_[index] = status;
|
||||
|
||||
// Nothing more to do if we can't send events.
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxLogDebug("Got Axis motion: %s ctrl_idx:%d val:%d prev_val:%d",
|
||||
wx_joystick_.ToString(), index, status, previous_status);
|
||||
|
||||
if (previous_status != wxAxisStatus::Neutral) {
|
||||
// Send the "unpressed" event.
|
||||
wxQueueEvent(handler, new wxJoyEvent(
|
||||
wx_joystick_,
|
||||
AxisStatusToJoyControl(previous_status),
|
||||
index,
|
||||
false));
|
||||
}
|
||||
|
||||
// We already sent the "unpressed" event so nothing more to do.
|
||||
if (status == wxAxisStatus::Neutral) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the "pressed" event.
|
||||
wxQueueEvent(handler, new wxJoyEvent(
|
||||
wx_joystick_,
|
||||
AxisStatusToJoyControl(status),
|
||||
index,
|
||||
true));
|
||||
|
||||
}
|
||||
|
||||
void wxSDLJoyState::ProcessButtonEvent(const uint8_t index, const bool status) {
|
||||
auto handler = wxGetApp().frame->GetJoyEventHandler();
|
||||
const bool previous_status = buttons_[index];
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
buttons_[index] = status;
|
||||
|
||||
// Nothing more to do if we can't send events.
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxLogDebug("Got Button event: %s ctrl_idx:%d val:%d prev_val:%d",
|
||||
wx_joystick_.ToString(), index, status, previous_status);
|
||||
|
||||
// Send the event.
|
||||
wxQueueEvent(handler, new wxJoyEvent(
|
||||
wx_joystick_, wxJoyControl::Button, index, status));
|
||||
}
|
||||
|
||||
void wxSDLJoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) {
|
||||
auto handler = wxGetApp().frame->GetJoyEventHandler();
|
||||
const uint16_t previous_status = hats_[index];
|
||||
|
||||
// Nothing to do if no-op.
|
||||
if (status == previous_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the value.
|
||||
hats_[index] = status;
|
||||
|
||||
// Nothing more to do if we can't send events.
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxLogDebug("Got Hat event: %s ctrl_idx:%d val:%d prev_val:%d",
|
||||
wx_joystick_.ToString(), index, status, previous_status);
|
||||
|
||||
// For HATs, the status value is a bit field, where each bit corresponds to
|
||||
// a direction. These are parsed here to send the corresponding "pressed"
|
||||
// and "unpressed" events.
|
||||
for (uint8_t bit = 0x01; bit != 0x10; bit <<= 1) {
|
||||
const bool old_control_pressed = (previous_status & bit) != 0;
|
||||
const bool new_control_pressed = (status & bit) != 0;
|
||||
if (old_control_pressed && !new_control_pressed) {
|
||||
// Send the "unpressed" event.
|
||||
wxQueueEvent(handler, new wxJoyEvent(
|
||||
wx_joystick_, HatStatusToJoyControl(bit), index, false));
|
||||
}
|
||||
if (!old_control_pressed && new_control_pressed) {
|
||||
// Send the "pressed" event.
|
||||
wxQueueEvent(handler, new wxJoyEvent(
|
||||
wx_joystick_, HatStatusToJoyControl(bit), index, true));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wxSDLJoyState::SetRumble(bool activate_rumble) {
|
||||
rumbling_ = activate_rumble;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
if (!game_controller_)
|
||||
return;
|
||||
|
||||
if (rumbling_) {
|
||||
SDL_GameControllerRumble(game_controller_, 0xFFFF, 0xFFFF, 300);
|
||||
if (!IsRunning())
|
||||
Start(150);
|
||||
} else {
|
||||
SDL_GameControllerRumble(game_controller_, 0, 0, 0);
|
||||
Stop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void wxSDLJoyState::Notify() {
|
||||
SetRumble(rumbling_);
|
||||
}
|
||||
|
||||
wxJoyPoller::wxJoyPoller() {
|
||||
// Start up joystick if not already started
|
||||
// FIXME: check for errors
|
||||
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
|
||||
SDL_GameControllerEventState(SDL_ENABLE);
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
}
|
||||
|
||||
wxJoyPoller::~wxJoyPoller() {
|
||||
// It is necessary to free all SDL resources before quitting SDL.
|
||||
joystick_states_.clear();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
}
|
||||
|
||||
void wxJoyPoller::Poll() {
|
||||
SDL_Event e;
|
||||
wxSDLJoyState* joy_state = nullptr;
|
||||
|
||||
while (SDL_PollEvent(&e)) {
|
||||
switch (e.type) {
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
joy_state = FindJoyState(e.cbutton.which);
|
||||
if (joy_state) {
|
||||
joy_state->ProcessButtonEvent(
|
||||
e.cbutton.button, e.cbutton.state);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
joy_state = FindJoyState(e.caxis.which);
|
||||
if (joy_state) {
|
||||
joy_state->ProcessAxisEvent(
|
||||
e.caxis.axis, AxisValueToStatus(e.caxis.value));
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Do nothing. This will be handled with JOYDEVICEADDED and
|
||||
// JOYDEVICEREMOVED events.
|
||||
break;
|
||||
|
||||
// Joystick events for non-GameControllers.
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
case SDL_JOYBUTTONUP:
|
||||
joy_state = FindJoyState(e.jbutton.which);
|
||||
if (joy_state && !joy_state->is_game_controller()) {
|
||||
joy_state->ProcessButtonEvent(
|
||||
e.jbutton.button, e.jbutton.state);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYAXISMOTION:
|
||||
joy_state = FindJoyState(e.jaxis.which);
|
||||
if (joy_state && !joy_state->is_game_controller()) {
|
||||
joy_state->ProcessAxisEvent(
|
||||
e.jaxis.axis, AxisValueToStatus(e.jaxis.value));
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYHATMOTION:
|
||||
joy_state = FindJoyState(e.jhat.which);
|
||||
if (joy_state && !joy_state->is_game_controller()) {
|
||||
joy_state->ProcessHatEvent(
|
||||
e.jhat.hat, e.jhat.value);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEADDED:
|
||||
// Always remap all controllers.
|
||||
RemapControllers();
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
joystick_states_.erase(e.jdevice.which);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wxJoyPoller::RemapControllers() {
|
||||
if (!is_polling_active_) {
|
||||
// Nothing to do when we're not actively polling.
|
||||
return;
|
||||
}
|
||||
|
||||
joystick_states_.clear();
|
||||
|
||||
if (requested_joysticks_.empty()) {
|
||||
// Connect all joysticks.
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
std::unique_ptr<wxSDLJoyState> joy_state(new wxSDLJoyState(i));
|
||||
if (joy_state->IsValid()) {
|
||||
joystick_states_.emplace(
|
||||
joy_state->joystick_id(), std::move(joy_state));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only attempt to add the joysticks we care about.
|
||||
for (const wxJoystick& joystick : requested_joysticks_) {
|
||||
std::unique_ptr<wxSDLJoyState> joy_state(
|
||||
new wxSDLJoyState(joystick));
|
||||
if (joy_state->IsValid()) {
|
||||
joystick_states_.emplace(
|
||||
joy_state->joystick_id(), std::move(joy_state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxSDLJoyState* wxJoyPoller::FindJoyState(const SDL_JoystickID& joy_id) {
|
||||
const auto iter = joystick_states_.find(joy_id);
|
||||
if (iter == joystick_states_.end())
|
||||
return nullptr;
|
||||
return iter->second.get();
|
||||
}
|
||||
|
||||
void wxJoyPoller::PollJoysticks(std::set<wxJoystick> joysticks) {
|
||||
// Reset the polling state.
|
||||
StopPolling();
|
||||
|
||||
if (joysticks.empty()) {
|
||||
// Nothing to poll. Return early.
|
||||
return;
|
||||
}
|
||||
|
||||
is_polling_active_ = true;
|
||||
requested_joysticks_ = joysticks;
|
||||
RemapControllers();
|
||||
}
|
||||
|
||||
void wxJoyPoller::PollAllJoysticks() {
|
||||
// Reset the polling state.
|
||||
StopPolling();
|
||||
is_polling_active_ = true;
|
||||
RemapControllers();
|
||||
}
|
||||
|
||||
void wxJoyPoller::StopPolling() {
|
||||
joystick_states_.clear();
|
||||
requested_joysticks_.clear();
|
||||
is_polling_active_ = false;
|
||||
}
|
||||
|
||||
void wxJoyPoller::SetRumble(bool activate_rumble) {
|
||||
if (joystick_states_.empty())
|
||||
return;
|
||||
|
||||
// Do rumble only on the first device.
|
||||
joystick_states_.begin()->second->SetRumble(activate_rumble);
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
#ifndef VBAM_WX_WIDGETS_SDLJOY_H_
|
||||
#define VBAM_WX_WIDGETS_SDLJOY_H_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <wx/time.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <SDL_joystick.h>
|
||||
#include <SDL_gamecontroller.h>
|
||||
#include <SDL_events.h>
|
||||
|
||||
// Forward declarations.
|
||||
class wxSDLJoyState;
|
||||
class wxJoyPoller;
|
||||
|
||||
// The different types of supported controls.
|
||||
enum wxJoyControl {
|
||||
AxisPlus = 0,
|
||||
AxisMinus,
|
||||
Button,
|
||||
HatNorth,
|
||||
HatSouth,
|
||||
HatWest,
|
||||
HatEast,
|
||||
Last = HatEast
|
||||
};
|
||||
|
||||
// Abstraction for a single joystick. In the current implementation, this
|
||||
// encapsulates an |sdl_index_|. Creation of these objects should only be
|
||||
// handled by wxSDLJoyState, with the exception of the legacy player_index
|
||||
// constructor, which should eventually go away.
|
||||
class wxJoystick {
|
||||
public:
|
||||
static wxJoystick Invalid();
|
||||
|
||||
// TODO: Remove this constructor once the transition to the new UserInput
|
||||
// type is complete.
|
||||
static wxJoystick FromLegacyPlayerIndex(unsigned player_index);
|
||||
|
||||
virtual ~wxJoystick() = default;
|
||||
|
||||
wxString ToString();
|
||||
|
||||
// TODO: Remove this API once the transition to the new UserInput type is
|
||||
// complete.
|
||||
unsigned player_index() { return sdl_index_ + 1; }
|
||||
|
||||
bool operator==(const wxJoystick& other) const;
|
||||
bool operator!=(const wxJoystick& other) const;
|
||||
bool operator<(const wxJoystick& other) const;
|
||||
bool operator<=(const wxJoystick& other) const;
|
||||
bool operator>(const wxJoystick& other) const;
|
||||
bool operator>=(const wxJoystick& other) const;
|
||||
|
||||
private:
|
||||
static const int kInvalidSdlIndex = -1;
|
||||
friend class wxSDLJoyState;
|
||||
|
||||
wxJoystick() = delete;
|
||||
wxJoystick(int sdl_index);
|
||||
|
||||
int sdl_index_;
|
||||
};
|
||||
|
||||
// Represents a Joystick event.
|
||||
class wxJoyEvent : public wxCommandEvent {
|
||||
public:
|
||||
wxJoyEvent(
|
||||
wxJoystick joystick,
|
||||
wxJoyControl control,
|
||||
uint8_t control_index,
|
||||
bool pressed);
|
||||
virtual ~wxJoyEvent() = default;
|
||||
|
||||
wxJoystick joystick() const { return joystick_; }
|
||||
wxJoyControl control() const { return control_; }
|
||||
uint8_t control_index() const { return control_index_; }
|
||||
bool pressed() const { return pressed_; }
|
||||
|
||||
private:
|
||||
wxJoystick joystick_;
|
||||
wxJoyControl control_;
|
||||
uint8_t control_index_;
|
||||
bool pressed_;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_JOY, wxJoyEvent);
|
||||
|
||||
typedef void (wxEvtHandler::*wxJoyEventFunction)(wxJoyEvent&);
|
||||
#define wxJoyEventHandler(func) wxEVENT_HANDLER_CAST(wxJoyEventFunction, func)
|
||||
#define EVT_SDLJOY(func) \
|
||||
wx__DECLARE_EVT0(wxEVT_JOY, wxJoyEventHandler(func))
|
||||
|
||||
// This is my own SDL-based joystick handler, since wxJoystick is brain-dead.
|
||||
// It's geared towards keyboard emulation.
|
||||
//
|
||||
// After initilization, use PollJoystick() or PollAllJoysticks() for the
|
||||
// joysticks you wish to monitor. The target window will then receive EVT_SDLJOY
|
||||
// events of type wxJoyEvent.
|
||||
// Handling of the wxJoystick value is different depending on the polling mode.
|
||||
// After calls to PollJoysticks(), that value will remain constant for a given
|
||||
// device, even if other joysticks disconnect. This ensures the joystick remains
|
||||
// active during gameplay even if other joysticks disconnect.
|
||||
// However, after calls to PollAllJoysticks(), all joysticks are re-connected
|
||||
// on joystick connect/disconnect. This ensures the right wxJoystick value is
|
||||
// sent to the UI during input event configuration.
|
||||
class wxJoyPoller {
|
||||
public:
|
||||
wxJoyPoller();
|
||||
~wxJoyPoller();
|
||||
|
||||
// Adds a set of joysticks to the list of polled joysticks.
|
||||
// This will disconnect every active joysticks, and reactivates the ones
|
||||
// matching an index in |joysticks|. Missing joysticks will be connected if
|
||||
// they connect later on.
|
||||
void PollJoysticks(std::set<wxJoystick> joysticks);
|
||||
|
||||
// Adds all joysticks to the list of polled joysticks. This will
|
||||
// disconnect every active joysticks, reconnect them and start polling.
|
||||
void PollAllJoysticks();
|
||||
|
||||
// Removes all joysticks from the list of polled joysticks.
|
||||
// This will stop polling.
|
||||
void StopPolling();
|
||||
|
||||
// Activates or deactivates rumble on active joysticks.
|
||||
void SetRumble(bool activate_rumble);
|
||||
|
||||
// Polls active joysticks and empties the SDL event buffer.
|
||||
void Poll();
|
||||
|
||||
private:
|
||||
// Reconnects all controllers.
|
||||
void RemapControllers();
|
||||
|
||||
// Helper method to find a joystick state from a joystick ID.
|
||||
// Returns nullptr if not present.
|
||||
wxSDLJoyState* FindJoyState(const SDL_JoystickID& joy_id);
|
||||
|
||||
// Map of SDL joystick ID to joystick state. Only contains active joysticks.
|
||||
std::unordered_map<SDL_JoystickID, std::unique_ptr<wxSDLJoyState>> joystick_states_;
|
||||
|
||||
// Set of requested SDL joystick indexes.
|
||||
std::set<wxJoystick> requested_joysticks_;
|
||||
|
||||
// Set to true when we are actively polling controllers.
|
||||
bool is_polling_active_ = false;
|
||||
|
||||
// Timestamp when the latest poll was done.
|
||||
wxLongLong last_poll_ = wxGetUTCTimeMillis();
|
||||
};
|
||||
|
||||
#endif // VBAM_WX_WIDGETS_SDLJOY_H_
|
|
@ -1,35 +1,13 @@
|
|||
#include "wx/widgets/user-input-ctrl.h"
|
||||
|
||||
#include <wx/time.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/opts.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
int FilterKeyCode(const wxKeyEvent& event) {
|
||||
const wxChar keycode = event.GetUnicodeKey();
|
||||
if (keycode == WXK_NONE) {
|
||||
return event.GetKeyCode();
|
||||
}
|
||||
|
||||
if (keycode < 32) {
|
||||
switch (keycode) {
|
||||
case WXK_BACK:
|
||||
case WXK_TAB:
|
||||
case WXK_RETURN:
|
||||
case WXK_ESCAPE:
|
||||
return keycode;
|
||||
default:
|
||||
return WXK_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return keycode;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern const char UserInputCtrlNameStr[] = "userinputctrl";
|
||||
|
||||
UserInputCtrl::UserInputCtrl() : wxTextCtrl() {}
|
||||
|
@ -53,6 +31,13 @@ bool UserInputCtrl::Create(wxWindow* parent,
|
|||
const wxSize& size,
|
||||
long style,
|
||||
const wxString& name) {
|
||||
this->SetClientObject(new UserInputEventSender(this));
|
||||
this->Bind(VBAM_EVT_USER_INPUT_UP, &UserInputCtrl::OnUserInputUp, this);
|
||||
this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) {
|
||||
is_navigating_away_ = false;
|
||||
last_focus_time_ = wxGetUTCTimeMillis();
|
||||
event.Skip();
|
||||
});
|
||||
return wxTextCtrl::Create(parent, id, value, pos, size, style, wxValidator(), name);
|
||||
}
|
||||
|
||||
|
@ -81,28 +66,18 @@ void UserInputCtrl::Clear() {
|
|||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl);
|
||||
|
||||
// clang-format off
|
||||
wxBEGIN_EVENT_TABLE(UserInputCtrl, wxTextCtrl)
|
||||
EVT_SDLJOY(UserInputCtrl::OnJoy)
|
||||
EVT_KEY_DOWN(UserInputCtrl::OnKeyDown)
|
||||
EVT_KEY_UP(UserInputCtrl::OnKeyUp)
|
||||
wxEND_EVENT_TABLE();
|
||||
// clang-format on
|
||||
|
||||
void UserInputCtrl::OnJoy(wxJoyEvent& event) {
|
||||
static wxLongLong last_event = 0;
|
||||
|
||||
// Filter consecutive axis motions within 300ms, as this adds two bindings
|
||||
// +1/-1 instead of the one intended.
|
||||
if ((event.control() == wxJoyControl::AxisPlus || event.control() == wxJoyControl::AxisMinus) &&
|
||||
wxGetUTCTimeMillis() - last_event < 300) {
|
||||
void UserInputCtrl::OnUserInputUp(UserInputEvent& event) {
|
||||
static const wxLongLong kInterval = 100;
|
||||
if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) {
|
||||
// Ignore events sent very shortly after focus. This is used to ignore
|
||||
// some spurious joystick events like an accidental axis motion.
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
last_event = wxGetUTCTimeMillis();
|
||||
|
||||
// Control was unpressed, ignore.
|
||||
if (!event.pressed()) {
|
||||
if (is_navigating_away_) {
|
||||
// Ignore events sent after the control has been navigated away from.
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -110,35 +85,10 @@ void UserInputCtrl::OnJoy(wxJoyEvent& event) {
|
|||
inputs_.clear();
|
||||
}
|
||||
|
||||
inputs_.emplace(event);
|
||||
UpdateText();
|
||||
Navigate();
|
||||
}
|
||||
|
||||
void UserInputCtrl::OnKeyDown(wxKeyEvent& event) {
|
||||
last_mod_ = event.GetModifiers();
|
||||
last_key_ = FilterKeyCode(event);
|
||||
}
|
||||
|
||||
void UserInputCtrl::OnKeyUp(wxKeyEvent&) {
|
||||
const int mod = last_mod_;
|
||||
const int key = last_key_;
|
||||
last_mod_ = last_key_ = 0;
|
||||
|
||||
// key is only 0 if we missed the keydown event
|
||||
// or if we are being shipped pseudo keypress events
|
||||
// either way, just ignore.
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_multikey_) {
|
||||
inputs_.clear();
|
||||
}
|
||||
|
||||
inputs_.emplace(key, mod);
|
||||
inputs_.insert(event.input());
|
||||
UpdateText();
|
||||
Navigate();
|
||||
is_navigating_away_ = true;
|
||||
}
|
||||
|
||||
void UserInputCtrl::UpdateText() {
|
||||
|
@ -153,6 +103,7 @@ void UserInputCtrl::UpdateText() {
|
|||
if (value.empty()) {
|
||||
SetValue(wxEmptyString);
|
||||
} else {
|
||||
// Remove the trailing comma.
|
||||
SetValue(value.substr(0, value.size() - 1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <set>
|
||||
|
||||
#include <wx/longlong.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/validate.h>
|
||||
|
@ -10,7 +11,7 @@
|
|||
|
||||
#include "wx/config/game-control.h"
|
||||
#include "wx/config/user-input.h"
|
||||
#include "wx/widgets/sdljoy.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
|
@ -59,20 +60,23 @@ public:
|
|||
void Clear() override;
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(UserInputCtrl);
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
|
||||
private:
|
||||
// Event handlers.
|
||||
void OnJoy(wxJoyEvent& event);
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
void OnKeyUp(wxKeyEvent& event);
|
||||
// Event handler.
|
||||
void OnUserInputUp(widgets::UserInputEvent& event);
|
||||
|
||||
// Updates the text in the control to reflect the current inputs.
|
||||
void UpdateText();
|
||||
|
||||
bool is_multikey_ = false;
|
||||
int last_mod_ = 0;
|
||||
int last_key_ = 0;
|
||||
|
||||
// The last time the control was focused. This is used to ignore events sent
|
||||
// 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::set<config::UserInput> inputs_;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
#include "wx/widgets/user-input-event.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
// Filters the received key code in the key event for something we can use.
|
||||
wxKeyCode FilterKeyCode(const wxKeyEvent& event) {
|
||||
const wxKeyCode unicode_key = static_cast<wxKeyCode>(event.GetUnicodeKey());
|
||||
if (unicode_key == WXK_NONE) {
|
||||
// We need to filter out modifier keys here so we can differentiate
|
||||
// between a key press and a modifier press.
|
||||
const wxKeyCode keycode = static_cast<wxKeyCode>(event.GetKeyCode());
|
||||
switch (keycode) {
|
||||
case WXK_CONTROL:
|
||||
case WXK_ALT:
|
||||
case WXK_SHIFT:
|
||||
#ifdef __WXMAC__
|
||||
case WXK_RAW_CONTROL:
|
||||
#endif
|
||||
return WXK_NONE;
|
||||
default:
|
||||
return keycode;
|
||||
}
|
||||
}
|
||||
|
||||
if (unicode_key < 32) {
|
||||
switch (unicode_key) {
|
||||
case WXK_BACK:
|
||||
case WXK_TAB:
|
||||
case WXK_RETURN:
|
||||
case WXK_ESCAPE:
|
||||
return unicode_key;
|
||||
default:
|
||||
return WXK_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return unicode_key;
|
||||
}
|
||||
|
||||
// Returns the set of modifiers for the given key event.
|
||||
std::unordered_set<wxKeyModifier> GetModifiers(const wxKeyEvent& event) {
|
||||
// Standalone modifier are treated as keys and do not set the keyboard modifiers.
|
||||
switch (event.GetKeyCode()) {
|
||||
case WXK_CONTROL:
|
||||
return {wxMOD_CONTROL};
|
||||
case WXK_ALT:
|
||||
return {wxMOD_ALT};
|
||||
case WXK_SHIFT:
|
||||
return {wxMOD_SHIFT};
|
||||
#ifdef __WXMAC__
|
||||
case WXK_RAW_CONTROL:
|
||||
return {wxMOD_RAW_CONTROL};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unordered_set<wxKeyModifier> mods;
|
||||
if (event.ControlDown()) {
|
||||
mods.insert(wxMOD_CONTROL);
|
||||
}
|
||||
if (event.AltDown()) {
|
||||
mods.insert(wxMOD_ALT);
|
||||
}
|
||||
if (event.ShiftDown()) {
|
||||
mods.insert(wxMOD_SHIFT);
|
||||
}
|
||||
#ifdef __WXMAC__
|
||||
if (event.RawControlDown()) {
|
||||
mods.insert(wxMOD_RAW_CONTROL);
|
||||
}
|
||||
#endif
|
||||
return mods;
|
||||
}
|
||||
|
||||
// Builds a wxKeyModifier from a set of modifiers.
|
||||
wxKeyModifier GetModifiersFromSet(const std::unordered_set<wxKeyModifier>& mods) {
|
||||
int mod = wxMOD_NONE;
|
||||
for (const wxKeyModifier m : mods) {
|
||||
mod |= m;
|
||||
}
|
||||
return static_cast<wxKeyModifier>(mod);
|
||||
}
|
||||
|
||||
// Returns the key code for a standalone modifier.
|
||||
wxKeyCode KeyFromModifier(const wxKeyModifier mod) {
|
||||
switch (mod) {
|
||||
case wxMOD_CONTROL:
|
||||
return WXK_CONTROL;
|
||||
case wxMOD_ALT:
|
||||
return WXK_ALT;
|
||||
case wxMOD_SHIFT:
|
||||
return WXK_SHIFT;
|
||||
#ifdef __WXMAC__
|
||||
case wxMOD_RAW_CONTROL:
|
||||
return WXK_RAW_CONTROL;
|
||||
#endif
|
||||
default:
|
||||
return WXK_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UserInputEvent::UserInputEvent(const config::UserInput& input, bool pressed)
|
||||
: wxEvent(0, pressed ? VBAM_EVT_USER_INPUT_DOWN : VBAM_EVT_USER_INPUT_UP), input_(input) {}
|
||||
|
||||
wxEvent* UserInputEvent::Clone() const {
|
||||
return new UserInputEvent(*this);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
UserInputEventSender::~UserInputEventSender() = default;
|
||||
|
||||
void UserInputEventSender::OnKeyDown(wxKeyEvent& event) {
|
||||
// Stop propagation of the event.
|
||||
event.Skip(false);
|
||||
|
||||
const wxKeyCode key = FilterKeyCode(event);
|
||||
const std::unordered_set<wxKeyModifier> mods = GetModifiers(event);
|
||||
|
||||
wxKeyCode key_pressed = WXK_NONE;
|
||||
if (key != WXK_NONE) {
|
||||
if (active_keys_.find(key) == active_keys_.end()) {
|
||||
// Key was not pressed before.
|
||||
key_pressed = key;
|
||||
active_keys_.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
wxKeyModifier mod_pressed = wxMOD_NONE;
|
||||
for (const wxKeyModifier mod : mods) {
|
||||
if (active_mods_.find(mod) == active_mods_.end()) {
|
||||
// Mod was not pressed before.
|
||||
active_mods_.insert(mod);
|
||||
mod_pressed = mod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key_pressed == WXK_NONE && mod_pressed == wxMOD_NONE) {
|
||||
// No new keys or mods were pressed.
|
||||
return;
|
||||
}
|
||||
|
||||
const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_);
|
||||
std::vector<config::UserInput> new_inputs;
|
||||
if (key_pressed == WXK_NONE) {
|
||||
// A new standalone modifier was pressed, send the event.
|
||||
new_inputs.emplace_back(KeyFromModifier(mod_pressed), mod_pressed);
|
||||
} else {
|
||||
// A new key was pressed, send the event with modifiers, first.
|
||||
new_inputs.emplace_back(key, active_mods);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for (const config::UserInput& input : new_inputs) {
|
||||
wxQueueEvent(window_, new UserInputEvent(input, true));
|
||||
}
|
||||
}
|
||||
|
||||
void UserInputEventSender::OnKeyUp(wxKeyEvent& event) {
|
||||
// Stop propagation of the event.
|
||||
event.Skip(false);
|
||||
|
||||
const wxKeyCode key = FilterKeyCode(event);
|
||||
const std::unordered_set<wxKeyModifier> mods = GetModifiers(event);
|
||||
const wxKeyModifier previous_mods = GetModifiersFromSet(active_mods_);
|
||||
|
||||
wxKeyCode key_released = WXK_NONE;
|
||||
if (key != WXK_NONE) {
|
||||
auto iter = active_keys_.find(key);
|
||||
if (iter != active_keys_.end()) {
|
||||
// Key was pressed before.
|
||||
key_released = key;
|
||||
active_keys_.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
wxKeyModifier mod_released = wxMOD_NONE;
|
||||
if (key_released == WXK_NONE) {
|
||||
// Only look for a standalone modifier if no key was released.
|
||||
for (const wxKeyModifier mod : mods) {
|
||||
auto iter = active_mods_.find(mod);
|
||||
if (iter != active_mods_.end()) {
|
||||
// Mod was pressed before.
|
||||
mod_released = mod;
|
||||
active_mods_.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key_released == WXK_NONE && mod_released == wxMOD_NONE) {
|
||||
// No keys or mods were released.
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<config::UserInput> released_inputs;
|
||||
if (key_released == WXK_NONE) {
|
||||
// A standalone modifier was released, send it.
|
||||
released_inputs.emplace_back(KeyFromModifier(mod_released), mod_released);
|
||||
} 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);
|
||||
} else {
|
||||
// Check if the key was pressed with the active modifiers.
|
||||
const config::UserInput 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));
|
||||
} else {
|
||||
active_mod_inputs_.erase(iter);
|
||||
}
|
||||
|
||||
// Send the key release event with the active modifiers.
|
||||
released_inputs.emplace_back(key, previous_mods);
|
||||
|
||||
// Also send the key release event without modifiers.
|
||||
released_inputs.emplace_back(key, wxMOD_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for any key that were pressed with the previously active
|
||||
// modifiers and release them.
|
||||
for (const wxKeyCode active_key : active_keys_) {
|
||||
const config::UserInput input(active_key, previous_mods);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const config::UserInput& input : released_inputs) {
|
||||
active_mod_inputs_.erase(input);
|
||||
wxQueueEvent(window_, new UserInputEvent(input, false));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace widgets
|
||||
|
||||
wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent);
|
||||
wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent);
|
|
@ -0,0 +1,67 @@
|
|||
#ifndef WX_WIDGETS_USER_INPUT_EVENT_H_
|
||||
#define WX_WIDGETS_USER_INPUT_EVENT_H_
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <wx/clntdata.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "wx/config/user-input.h"
|
||||
|
||||
namespace widgets {
|
||||
|
||||
// Event fired when a user input is pressed or released. The event contains the
|
||||
// user input that was pressed or released.
|
||||
class UserInputEvent final : public wxEvent {
|
||||
public:
|
||||
UserInputEvent(const config::UserInput& input, bool pressed);
|
||||
virtual ~UserInputEvent() override = default;
|
||||
|
||||
// wxEvent implementation.
|
||||
wxEvent* Clone() const override;
|
||||
|
||||
const config::UserInput& input() const { return input_; }
|
||||
|
||||
private:
|
||||
const config::UserInput input_;
|
||||
};
|
||||
|
||||
// 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 {
|
||||
public:
|
||||
// `window` must not be nullptr. Will assert otherwise.
|
||||
// `window` must outlive this object.
|
||||
explicit UserInputEventSender(wxWindow* const window);
|
||||
~UserInputEventSender() override;
|
||||
|
||||
// Disable copy and copy assignment.
|
||||
UserInputEventSender(const UserInputEventSender&) = delete;
|
||||
UserInputEventSender& operator=(const UserInputEventSender&) = delete;
|
||||
|
||||
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::set<config::UserInput> active_mod_inputs_;
|
||||
|
||||
// The wxWindow this object is attached to.
|
||||
// Must outlive this object.
|
||||
wxWindow* const window_;
|
||||
};
|
||||
|
||||
} // 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);
|
||||
|
||||
#endif // WX_WIDGETS_USER_INPUT_EVENT_H_
|
|
@ -57,8 +57,6 @@ using std::int32_t;
|
|||
// GetAccel is inefficent anyway (often I don't want to convert to wxAccEnt)
|
||||
// This is a working replacement for SetAccel, at least.
|
||||
|
||||
#include "wx/wxutil.h"
|
||||
|
||||
// wxrc helpers (for dynamic strings instead of constant)
|
||||
#define XRCID_D(str) wxXmlResource::GetXRCID(str)
|
||||
//#define XRCCTRL_D(win, id, type) (wxStaticCast((win).FindWindow(XRCID_D(id)), type))
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
#include "wx/wxutil.h"
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#ifndef VBAM_WX_UTIL_H_
|
||||
#define VBAM_WX_UTIL_H_
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
int getKeyboardKeyCode(const wxKeyEvent& event);
|
||||
|
||||
#endif // VBAM_WX_UTIL_H_
|
|
@ -1,14 +1,13 @@
|
|||
// mainline:
|
||||
// parse cmd line
|
||||
// load xrc file (guiinit.cpp does most of instantiation)
|
||||
// create & display main frame
|
||||
|
||||
#include "wx/wxvbam.h"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gdk/gdk.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <wx/cmdline.h>
|
||||
#include <wx/display.h>
|
||||
|
@ -31,18 +30,9 @@
|
|||
#include <wx/zipstrm.h>
|
||||
|
||||
#include "components/user_config/user_config.h"
|
||||
#include "core/gb/gbGlobals.h"
|
||||
#include "core/gba/gbaSound.h"
|
||||
|
||||
#if defined(VBAM_ENABLE_DEBUGGER)
|
||||
#include "core/gba/gbaRemote.h"
|
||||
#endif // defined(VBAM_ENABLE_DEBUGGER)
|
||||
|
||||
// The built-in xrc file
|
||||
#include "wx/builtin-xrc.h"
|
||||
|
||||
// The built-in vba-over.ini
|
||||
#include "wx/builtin-over.h"
|
||||
#include "wx/builtin-xrc.h"
|
||||
#include "wx/config/game-control.h"
|
||||
#include "wx/config/option-proxy.h"
|
||||
#include "wx/config/option.h"
|
||||
|
@ -53,9 +43,9 @@
|
|||
#include "wx/widgets/user-input-ctrl.h"
|
||||
#include "wx/widgets/utils.h"
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gdk/gdk.h>
|
||||
#endif
|
||||
#if defined(VBAM_ENABLE_DEBUGGER)
|
||||
#include "core/gba/gbaRemote.h"
|
||||
#endif // defined(VBAM_ENABLE_DEBUGGER)
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -261,6 +251,13 @@ static void tack_full_path(wxString& s, const wxString& app = wxEmptyString)
|
|||
s += wxT("\n\t") + full_config_path[i] + app;
|
||||
}
|
||||
|
||||
wxvbamApp::wxvbamApp()
|
||||
: wxApp(),
|
||||
pending_fullscreen(false),
|
||||
frame(nullptr),
|
||||
using_wayland(false),
|
||||
sdl_poller_(widgets::SdlPoller(std::bind(&wxvbamApp::GetJoyEventHandler, this))) {}
|
||||
|
||||
const wxString wxvbamApp::GetPluginsDir()
|
||||
{
|
||||
return wxStandardPaths::Get().GetPluginsDir();
|
||||
|
@ -836,6 +833,30 @@ wxvbamApp::~wxvbamApp() {
|
|||
#endif
|
||||
}
|
||||
|
||||
wxEvtHandler* wxvbamApp::GetJoyEventHandler() {
|
||||
// Use the active window, if any.
|
||||
wxWindow* focused_window = wxWindow::FindFocus();
|
||||
if (focused_window) {
|
||||
return focused_window;
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto panel = frame->GetPanel();
|
||||
if (!panel || !panel->panel) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (OPTION(kUIAllowJoystickBackgroundInput)) {
|
||||
// Use the game panel, if the background polling option is enabled.
|
||||
return panel->panel->GetWindow()->GetEventHandler();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MainFrame::MainFrame()
|
||||
: wxFrame(),
|
||||
paused(false),
|
||||
|
@ -849,8 +870,6 @@ MainFrame::MainFrame()
|
|||
keep_on_top_styler_(this),
|
||||
status_bar_observer_(config::OptionID::kGenStatusBar,
|
||||
std::bind(&MainFrame::OnStatusBarChanged, this)) {
|
||||
jpoll = new JoystickPoller();
|
||||
this->Connect(wxID_ANY, wxEVT_SHOW, wxShowEventHandler(JoystickPoller::ShowDialog), jpoll, jpoll);
|
||||
}
|
||||
|
||||
MainFrame::~MainFrame() {
|
||||
|
@ -983,19 +1002,15 @@ int MainFrame::FilterEvent(wxEvent& event) {
|
|||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
int command = 0;
|
||||
if (event.GetEventType() == wxEVT_KEY_DOWN) {
|
||||
const wxKeyEvent& key_event = static_cast<wxKeyEvent&>(event);
|
||||
command = gopts.shortcuts.CommandForInput(config::UserInput(key_event));
|
||||
} else if (event.GetEventType() == wxEVT_JOY) {
|
||||
const wxJoyEvent& joy_event = static_cast<wxJoyEvent&>(event);
|
||||
if (joy_event.pressed()) {
|
||||
// We ignore "button up" events here.
|
||||
command = gopts.shortcuts.CommandForInput(config::UserInput(joy_event));
|
||||
}
|
||||
if (event.GetEventType() != VBAM_EVT_USER_INPUT_DOWN) {
|
||||
// We only treat "VBAM_EVT_USER_INPUT_DOWN" events here.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
const widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event);
|
||||
const int command = gopts.shortcuts.CommandForInput(user_input_event.input());
|
||||
if (command == 0) {
|
||||
// No associated command found.
|
||||
return wxEventFilter::Event_Skip;
|
||||
}
|
||||
|
||||
|
@ -1028,58 +1043,6 @@ wxString MainFrame::GetGamePath(wxString path)
|
|||
return game_path;
|
||||
}
|
||||
|
||||
void MainFrame::SetJoystick()
|
||||
{
|
||||
/* Remove all attached joysticks to avoid errors while
|
||||
* destroying and creating the GameArea `panel`. */
|
||||
joy.StopPolling();
|
||||
|
||||
if (!emulating)
|
||||
return;
|
||||
|
||||
std::set<wxJoystick> needed_joysticks = gopts.shortcuts.Joysticks();
|
||||
for (const auto& iter : gopts.game_control_bindings) {
|
||||
for (const auto& input_iter : iter.second) {
|
||||
needed_joysticks.emplace(input_iter.joystick());
|
||||
}
|
||||
}
|
||||
joy.PollJoysticks(std::move(needed_joysticks));
|
||||
}
|
||||
|
||||
void MainFrame::StopJoyPollTimer()
|
||||
{
|
||||
if (jpoll && jpoll->IsRunning())
|
||||
jpoll->Stop();
|
||||
}
|
||||
|
||||
void MainFrame::StartJoyPollTimer()
|
||||
{
|
||||
if (jpoll && !jpoll->IsRunning())
|
||||
jpoll->Start();
|
||||
}
|
||||
|
||||
bool MainFrame::IsJoyPollTimerRunning()
|
||||
{
|
||||
return jpoll->IsRunning();
|
||||
}
|
||||
|
||||
wxEvtHandler* MainFrame::GetJoyEventHandler()
|
||||
{
|
||||
auto focused_window = wxWindow::FindFocus();
|
||||
|
||||
if (focused_window)
|
||||
return focused_window;
|
||||
|
||||
auto panel = GetPanel();
|
||||
if (!panel)
|
||||
return nullptr;
|
||||
|
||||
if (OPTION(kUIAllowJoystickBackgroundInput))
|
||||
return panel->GetEventHandler();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MainFrame::enable_menus()
|
||||
{
|
||||
for (int i = 0; i < ncmds; i++)
|
||||
|
@ -1403,7 +1366,8 @@ void MainFrame::IdentifyRom()
|
|||
// grabbing those keys, but I can't track it down.
|
||||
int wxvbamApp::FilterEvent(wxEvent& event)
|
||||
{
|
||||
if (frame)
|
||||
if (frame) {
|
||||
return frame->FilterEvent(event);
|
||||
}
|
||||
return wxApp::FilterEvent(event);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
#include "wx/dialogs/base-dialog.h"
|
||||
#include "wx/widgets/dpi-support.h"
|
||||
#include "wx/widgets/keep-on-top-styler.h"
|
||||
#include "wx/widgets/sdljoy.h"
|
||||
#include "wx/widgets/sdl-poller.h"
|
||||
#include "wx/widgets/user-input-event.h"
|
||||
#include "wx/widgets/wxmisc.h"
|
||||
#include "wx/wxhead.h"
|
||||
|
||||
|
@ -30,7 +31,6 @@
|
|||
#endif
|
||||
|
||||
#include "wx/wxlogdebug.h"
|
||||
#include "wx/wxutil.h"
|
||||
|
||||
template <typename T>
|
||||
void CheckPointer(T pointer)
|
||||
|
@ -59,13 +59,7 @@ class MainFrame;
|
|||
|
||||
class wxvbamApp : public wxApp {
|
||||
public:
|
||||
wxvbamApp()
|
||||
: wxApp()
|
||||
, pending_fullscreen(false)
|
||||
, frame(NULL)
|
||||
, using_wayland(false)
|
||||
{
|
||||
}
|
||||
wxvbamApp();
|
||||
virtual bool OnInit();
|
||||
virtual int OnRun();
|
||||
virtual bool OnCmdLineHelp(wxCmdLineParser&);
|
||||
|
@ -94,6 +88,8 @@ public:
|
|||
// without this, global accels don't always work
|
||||
int FilterEvent(wxEvent&);
|
||||
|
||||
widgets::SdlPoller* sdl_poller() { return &sdl_poller_; }
|
||||
|
||||
// vba-over.ini
|
||||
wxFileConfig* overrides = nullptr;
|
||||
|
||||
|
@ -130,9 +126,14 @@ protected:
|
|||
int console_status = 0;
|
||||
|
||||
private:
|
||||
// Returns the currently active event handler to use for user input events.
|
||||
wxEvtHandler* GetJoyEventHandler();
|
||||
|
||||
wxPathList config_path;
|
||||
char* home = nullptr;
|
||||
|
||||
widgets::SdlPoller sdl_poller_;
|
||||
|
||||
// Main configuration file.
|
||||
wxFileName config_file_;
|
||||
};
|
||||
|
@ -216,8 +217,6 @@ public:
|
|||
void GetMenuOptionBool(const wxString& menuName, bool* field);
|
||||
void SetMenuOption(const wxString& menuName, bool value);
|
||||
|
||||
void SetJoystick();
|
||||
|
||||
int FilterEvent(wxEvent& event);
|
||||
|
||||
GameArea* GetPanel()
|
||||
|
@ -308,24 +307,11 @@ public:
|
|||
|
||||
virtual bool DialogOpened() { return dialog_opened != 0; }
|
||||
|
||||
virtual void SetJoystickRumble(bool b) { joy.SetRumble(b); }
|
||||
|
||||
bool IsPaused(bool incendental = false)
|
||||
{
|
||||
return (paused && !pause_next && !incendental) || dialog_opened;
|
||||
}
|
||||
|
||||
void PollJoysticks() { joy.Poll(); }
|
||||
|
||||
void PollAllJoysticks() { joy.PollAllJoysticks(); }
|
||||
|
||||
// Poll joysticks with timer.
|
||||
void StartJoyPollTimer();
|
||||
void StopJoyPollTimer();
|
||||
bool IsJoyPollTimerRunning();
|
||||
|
||||
wxEvtHandler* GetJoyEventHandler();
|
||||
|
||||
// required for building from xrc
|
||||
DECLARE_DYNAMIC_CLASS(MainFrame);
|
||||
// required for event handling
|
||||
|
@ -351,9 +337,6 @@ private:
|
|||
checkable_mi_array_t checkable_mi;
|
||||
// recent menu item accels
|
||||
wxMenu* recent;
|
||||
// joystick reader
|
||||
wxJoyPoller joy;
|
||||
JoystickPoller* jpoll = nullptr;
|
||||
// quicker & more accurate than FindFocus() != NULL
|
||||
bool focused;
|
||||
// One-time toggle to indicate that this object is fully initialized. This
|
||||
|
@ -390,20 +373,6 @@ private:
|
|||
#include "wx/cmdhandlers.h"
|
||||
};
|
||||
|
||||
// a class for polling joystick keys
|
||||
class JoystickPoller : public wxTimer {
|
||||
public:
|
||||
void Notify() {
|
||||
wxGetApp().frame->PollJoysticks();
|
||||
}
|
||||
void ShowDialog(wxShowEvent& ev) {
|
||||
if (ev.IsShown())
|
||||
Start(50);
|
||||
else
|
||||
Stop();
|
||||
}
|
||||
};
|
||||
|
||||
// a helper class to avoid forgetting StopModal()
|
||||
class ModalPause {
|
||||
public:
|
||||
|
@ -581,9 +550,8 @@ protected:
|
|||
|
||||
bool paused;
|
||||
void OnIdle(wxIdleEvent&);
|
||||
void OnKeyDown(wxKeyEvent& ev);
|
||||
void OnKeyUp(wxKeyEvent& ev);
|
||||
void OnSDLJoy(wxJoyEvent& ev);
|
||||
void OnUserInputDown(widgets::UserInputEvent& event);
|
||||
void OnUserInputUp(widgets::UserInputEvent& event);
|
||||
void PaintEv(wxPaintEvent& ev);
|
||||
void EraseBackground(wxEraseEvent& ev);
|
||||
void OnSize(wxSizeEvent& ev);
|
||||
|
|
Loading…
Reference in New Issue