Introduce wxUserInput class

This introduces a new abstraction for any user input. The long-term goal
is to replace every usage of "custom" {key, mod, joy} triplets in the
code base with this new class.

This class implements comparison operators, allowing for faster access
in a set or as a key in a map, compared to the vectors currently used.

Issue: #745
This commit is contained in:
Fabrice de Gans 2022-07-09 13:17:35 -07:00 committed by Rafael Kitover
parent d7abc27438
commit 4aebd0b802
9 changed files with 248 additions and 90 deletions

View File

@ -728,6 +728,7 @@ set(
wxutil.cpp
widgets/keyedit.cpp
widgets/joyedit.cpp
widgets/userinput.cpp
widgets/sdljoy.cpp
widgets/wxmisc.cpp
# probably ought to be in common
@ -761,6 +762,7 @@ set(
widgets/wx/keyedit.h
widgets/wx/joyedit.h
widgets/wx/sdljoy.h
widgets/wx/userinput.h
widgets/wx/webupdatedef.h
widgets/wx/wxmisc.h
# probably ought to be in common

View File

@ -23,6 +23,8 @@
#include "../sdl/text.h"
#include "drawing.h"
#include "filters.h"
#include "wx/joyedit.h"
#include "wx/userinput.h"
#include "wxvbam.h"
#include "wxutil.h"
#include "wayland.h"
@ -1217,7 +1219,7 @@ static uint32_t bmask[NUM_KEYS] = {
KEYM_GS
};
static wxJoyKeyBinding_v keys_pressed;
static std::set<wxUserInput> keys_pressed;
static void clear_input_press()
{
@ -1236,6 +1238,7 @@ struct game_key {
wxJoyKeyBinding_v& b;
};
// Populates |vec| with the game keys currently pressed.
static void game_keys_pressed(int key, int mod, int joy, std::vector<game_key>* vec)
{
for (int player = 0; player < 4; player++)
@ -1248,56 +1251,32 @@ static void game_keys_pressed(int key, int mod, int joy, std::vector<game_key>*
}
}
static bool process_key_press(bool down, int key, int mod, int joy = 0)
static bool process_user_input(bool down, const wxUserInput& user_input)
{
// 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:
if (joy == 0) mod = 0;
break;
}
// check if key is already pressed
size_t kpno;
for (kpno = 0; kpno < keys_pressed.size(); kpno++)
if (keys_pressed[kpno].key == key && keys_pressed[kpno].mod == mod && keys_pressed[kpno].joy == joy)
break;
int key = user_input.key();
int mod = user_input.mod();
int joy = user_input.joy();
std::vector<game_key> game_keys;
game_keys_pressed(key, mod, joy, &game_keys);
const bool is_game_key = !game_keys.empty();
const bool is_game_key = game_keys.size();
if (kpno < keys_pressed.size()) {
// check if key is already pressed
auto iter = keys_pressed.find(user_input);
if (iter != keys_pressed.end()) {
// double press is noop
if (down)
return is_game_key;
// if released, forget it
keys_pressed.erase(keys_pressed.begin() + kpno);
iter = keys_pressed.erase(iter);
} else {
// double release is noop
if (!down)
return is_game_key;
// otherwise remember it
keys_pressed.push_back({key, mod, joy});
keys_pressed.emplace(user_input);
}
for (auto&& game_key : game_keys) {
@ -1313,12 +1292,7 @@ static bool process_key_press(bool down, int key, int mod, int joy = 0)
for (bind2 = 0; bind2 < game_key.b.size(); bind2++) {
if ((size_t)game_key.bind_num == bind2 || (b[bind2].key == key && b[bind2].mod == mod && b[bind2].joy == joy))
continue;
for (kpno = 0; kpno < keys_pressed.size(); kpno++)
if (keys_pressed[kpno].key == b[bind2].key && keys_pressed[kpno].mod == b[bind2].mod && keys_pressed[kpno].joy == b[bind2].joy)
break;
}
if (bind2 == b.size()) {
// release button
joypress[game_key.player] &= ~bmask[game_key.key_num];
@ -1338,42 +1312,63 @@ static void draw_black_background(wxWindow* win) {
dc.DrawRectangle(0, 0, w, h);
}
static bool is_key_pressed(wxKeyEvent& ev)
static void process_keyboard_event(const wxKeyEvent& ev, bool down)
{
auto kc = ev.GetKeyCode();
int kc = ev.GetKeyCode();
// Under Wayland or if the key is unicode, we can't use wxGetKeyState().
if (IsItWayland() || kc == WXK_NONE)
return true;
return wxGetKeyState(static_cast<wxKeyCode>(kc));
if (!IsItWayland() && kc != WXK_NONE) {
// Check if the key state corresponds to the event.
if (down != wxGetKeyState(static_cast<wxKeyCode>(kc))) {
return;
}
}
static bool is_key_released(wxKeyEvent& ev)
{
auto kc = ev.GetKeyCode();
int key = getKeyboardKeyCode(ev);
int mod = ev.GetModifiers();
// Under Wayland or if the key is unicode, we can't use wxGetKeyState().
if (IsItWayland() || kc == WXK_NONE)
return true;
if (key == WXK_NONE) {
return;
}
return !wxGetKeyState(static_cast<wxKeyCode>(kc));
// 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, wxUserInput::FromLegacyJoyKeyBinding({key, mod, 0}))) {
wxWakeUpIdle();
};
}
void GameArea::OnKeyDown(wxKeyEvent& ev)
{
int kc = getKeyboardKeyCode(ev);
if (is_key_pressed(ev) && process_key_press(true, kc, ev.GetModifiers())) {
wxWakeUpIdle();
}
process_keyboard_event(ev, true);
}
void GameArea::OnKeyUp(wxKeyEvent& ev)
{
int kc = getKeyboardKeyCode(ev);
if (is_key_released(ev) && process_key_press(false, kc, ev.GetModifiers())) {
wxWakeUpIdle();
}
process_keyboard_event(ev, false);
}
// these three are forwarded to the DrawingPanel instance
@ -1406,20 +1401,22 @@ void GameArea::OnSDLJoy(wxJoyEvent& ev)
int joy = ev.joystick().player_index();
// mutually exclusive key types unpress their opposite
// TODO: Move creation of these "ghost" events to wxJoyPoller.
if (mod == WXJB_AXIS_PLUS) {
process_key_press(false, key, WXJB_AXIS_MINUS, joy);
process_key_press(ev.control_value() != 0, key, mod, joy);
process_user_input(false, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_AXIS_MINUS, joy}));
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
} else if (mod == WXJB_AXIS_MINUS) {
process_key_press(false, key, WXJB_AXIS_PLUS, joy);
process_key_press(ev.control_value() != 0, key, mod, joy);
process_user_input(false, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_AXIS_PLUS, joy}));
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
} else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST) {
int value = ev.control_value();
process_key_press(value & SDL_HAT_UP, key, WXJB_HAT_N, joy);
process_key_press(value & SDL_HAT_DOWN, key, WXJB_HAT_S, joy);
process_key_press(value & SDL_HAT_RIGHT, key, WXJB_HAT_E, joy);
process_key_press(value & SDL_HAT_LEFT, key, WXJB_HAT_W, joy);
} else
process_key_press(ev.control_value() != 0, key, mod, joy);
process_user_input(value & SDL_HAT_UP, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_N, joy}));
process_user_input(value & SDL_HAT_DOWN, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_S, joy}));
process_user_input(value & SDL_HAT_RIGHT, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_E, joy}));
process_user_input(value & SDL_HAT_LEFT, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_W, joy}));
} else {
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
}
// tell Linux to turn off the screensaver/screen-blank if joystick button was pressed
// this shouldn't be necessary of course

View File

@ -1,6 +1,9 @@
#include <wx/tokenzr.h>
#include "wx/joyedit.h"
#include <wx/tokenzr.h>
#include "strutils.h"
#include "wx/userinput.h"
// FIXME: suppport analog/digital flag on per-axis basis
@ -17,7 +20,7 @@ wxJoyKeyBinding newWxJoyKeyBinding(int key, int mod, int joy)
return tmp;
}
int wxJoyKeyTextCtrl::DigitalButton(wxJoyEvent& event)
int wxJoyKeyTextCtrl::DigitalButton(const wxJoyEvent& event)
{
int16_t sdlval = event.control_value();
wxJoyControl sdltype = event.control();
@ -72,24 +75,17 @@ void wxJoyKeyTextCtrl::OnJoy(wxJoyEvent& event)
{
static wxLongLong last_event = 0;
int16_t val = event.control_value();
wxJoyControl type = event.control();
// Filter consecutive axis motions within 300ms, as this adds two bindings
// +1/-1 instead of the one intended.
if (type == wxJoyControl::Axis && wxGetUTCTimeMillis() - last_event < 300)
if (event.control() == wxJoyControl::Axis && wxGetUTCTimeMillis() - last_event < 300)
return;
last_event = wxGetUTCTimeMillis();
int mod = DigitalButton(event);
uint8_t key = event.control_index();
unsigned joy = event.joystick().player_index();
if (!val || mod < 0)
if (!event.control_value() || DigitalButton(event) < 0)
return;
wxString nv = ToString(mod, key, joy);
wxString nv = wxUserInput::FromJoyEvent(event).ToString();
if (nv.empty())
return;

View File

@ -0,0 +1,94 @@
#include "wx/userinput.h"
#include "wx/string.h"
#include "wxutil.h"
// static
wxUserInput wxUserInput::Invalid() {
return wxUserInput(Device::Invalid, 0, 0, 0);
}
// static
wxUserInput wxUserInput::FromKeyEvent(const wxKeyEvent& event) {
return wxUserInput(Device::Keyboard,
event.GetModifiers(),
getKeyboardKeyCode(event),
0);
}
// static
wxUserInput wxUserInput::FromJoyEvent(const wxJoyEvent& event) {
return wxUserInput(Device::Joystick,
wxJoyKeyTextCtrl::DigitalButton(event),
event.control_index(),
event.joystick().player_index());
}
// static
wxUserInput wxUserInput::FromLegacyJoyKeyBinding(const wxJoyKeyBinding& binding) {
return wxUserInput(binding.joy == 0 ? Device::Keyboard : Device::Joystick,
binding.mod,
binding.key,
binding.joy);
}
// static
wxUserInput wxUserInput::FromString(const wxString& string) {
// TODO: Move the implementation here once all callers have been updated.
int mod = 0;
int key = 0;
int joy = 0;
if (!wxJoyKeyTextCtrl::FromString(string, mod, key, joy)) {
return wxUserInput::Invalid();
}
return wxUserInput::FromLegacyJoyKeyBinding({key, mod, joy});
}
wxString wxUserInput::ToString() {
if (!config_string_.IsNull()) {
return config_string_;
}
// TODO: Move the implementation here once all callers have been updated.
config_string_ = wxJoyKeyTextCtrl::ToString(mod_, key_, joy_);
return config_string_;
}
bool wxUserInput::operator==(const wxUserInput& other) const {
return device_ == other.device_ && mod_ == other.mod_ &&
key_ == other.key_ && joy_ == other.joy_;
}
bool wxUserInput::operator!=(const wxUserInput& other) const {
return !(*this == other);
}
bool wxUserInput::operator<(const wxUserInput& other) const {
if (device_ < other.device_) {
return device_ < other.device_;
}
if (joy_ != other.joy_) {
return joy_ < other.joy_;
}
if (key_ != other.key_) {
return key_ < other.key_;
}
if (mod_ != other.mod_) {
return mod_ < other.mod_;
}
return false;
}
bool wxUserInput::operator<=(const wxUserInput& other) const {
return !(*this > other);
}
bool wxUserInput::operator>(const wxUserInput& other) const {
return other < *this;
}
bool wxUserInput::operator>=(const wxUserInput& other) const {
return !(*this < other);
}
// Actual underlying constructor.
wxUserInput::wxUserInput(Device device, int mod, uint8_t key, unsigned joy) :
device_(device),
mod_(mod),
key_(key),
joy_(joy) {}

View File

@ -49,7 +49,7 @@ public:
// key is event.GetControlIndex(), and joy is event.GetJoy() + 1
// mod is derived from GetControlValue() and GetControlType():
// convert wxJoyEvent's type+val into mod (WXJB_*)
static int DigitalButton(wxJoyEvent& event);
static int DigitalButton(const wxJoyEvent& event);
// convert mod+key to accel string, separated by -
static wxString ToString(int mod, int key, int joy, bool isConfig = false);
// convert multiple keys, separated by multikey

View File

@ -0,0 +1,71 @@
#ifndef _WX_USER_INPUT_H_
#define _WX_USER_INPUT_H_
#include <optional>
#include "wx/joyedit.h"
#include "wx/sdljoy.h"
// 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.
//
// TODO: Right now, this class is implemented as a thin wrapper around the key,
// mod and joy user input representation used in many places in the code base.
// This is to ease a transition away from the wxJoyKeyBinding type, which
// wxUserInput will eventually replace.
class wxUserInput {
public:
// The device type for a user control.
enum class Device {
Invalid = 0,
Keyboard,
Joystick,
Last = Joystick
};
// Invalid wxUserInput, mainly used for comparison.
static wxUserInput Invalid();
// Constructor from a wxKeyEvent.
static wxUserInput FromKeyEvent(const wxKeyEvent& event);
// Constructor from a wxJoyEvent.
static wxUserInput FromJoyEvent(const wxJoyEvent& event);
// Constructor from a configuration string. Returns wxUserInput::Invalid()
// on parsing failure.
static wxUserInput FromString(const wxString& string);
// TODO: Remove this once all uses of wxJoyKeyBinding have been removed.
static wxUserInput FromLegacyJoyKeyBinding(const wxJoyKeyBinding& binding);
// Converts to a configuration string. Computed on first call, and cached
// for further calls.
wxString ToString();
// TODO: Remove these accessors once all callers have been removed.
int mod() const { return mod_; }
int key() const { return key_; }
int joy() const { return joy_; }
bool operator==(const wxUserInput& other) const;
bool operator!=(const wxUserInput& other) const;
bool operator<(const wxUserInput& other) const;
bool operator<=(const wxUserInput& other) const;
bool operator>(const wxUserInput& other) const;
bool operator>=(const wxUserInput& other) const;
private:
wxUserInput(Device device, int mod, uint8_t key, unsigned joy);
Device device_;
int mod_;
uint8_t key_;
unsigned joy_;
wxString config_string_;
};
#endif /* _WX_USER_INPUT_H */

View File

@ -2,7 +2,7 @@
#include "../common/contains.h"
int getKeyboardKeyCode(wxKeyEvent& event)
int getKeyboardKeyCode(const wxKeyEvent& event)
{
int uc = event.GetUnicodeKey();
if (uc != WXK_NONE) {

View File

@ -3,7 +3,7 @@
#include <wx/event.h>
int getKeyboardKeyCode(wxKeyEvent& event);
int getKeyboardKeyCode(const wxKeyEvent& event);
#include <wx/accel.h>

View File

@ -27,6 +27,7 @@
// The built-in vba-over.ini
#include "builtin-over.h"
#include "wx/userinput.h"
IMPLEMENT_APP(wxvbamApp)
IMPLEMENT_DYNAMIC_CLASS(MainFrame, wxFrame)
@ -881,10 +882,7 @@ int MainFrame::FilterEvent(wxEvent& event)
{
wxJoyEvent& je = (wxJoyEvent&)event;
if (je.control_value() == 0) return -1; // joystick button UP
uint8_t key = je.control_index();
int mod = wxJoyKeyTextCtrl::DigitalButton(je);
int joy = je.joystick().player_index();
wxString label = wxJoyKeyTextCtrl::ToString(mod, key, joy);
wxString label = wxUserInput::FromJoyEvent(je).ToString();
wxAcceleratorEntry_v accels = wxGetApp().GetAccels();
for (size_t i = 0; i < accels.size(); ++i)
{