mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-09 00:11:17 +12:00
input: controller support for configurable hotkeys
This commit is contained in:
parent
dc2d0901e6
commit
c8f5c2c528
13 changed files with 407 additions and 98 deletions
|
@ -345,11 +345,12 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||||
|
|
||||||
// hotkeys
|
// hotkeys
|
||||||
auto xml_hotkeys = parser.get("Hotkeys");
|
auto xml_hotkeys = parser.get("Hotkeys");
|
||||||
hotkeys.exitFullscreen.raw = xml_hotkeys.get("ExitFullscreen", WXK_ESCAPE);
|
hotkeys.modifiers = xml_hotkeys.get("modifiers", sHotkeyCfg{});
|
||||||
hotkeys.toggleFullscreen.raw = xml_hotkeys.get("ToggleFullscreen", WXK_F11);
|
hotkeys.exitFullscreen = xml_hotkeys.get("ExitFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_ESCAPE}});
|
||||||
hotkeys.toggleFullscreenAlt.raw = xml_hotkeys.get("ToggleFullscreenAlt", uHotkey{WXK_CONTROL_M, true}.raw); // ALT+ENTER
|
hotkeys.toggleFullscreen = xml_hotkeys.get("ToggleFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_F11}});
|
||||||
hotkeys.takeScreenshot.raw = xml_hotkeys.get("TakeScreenshot", WXK_F12);
|
hotkeys.toggleFullscreenAlt = xml_hotkeys.get("ToggleFullscreenAlt", sHotkeyCfg{uKeyboardHotkey{WXK_CONTROL_M, true}}); // ALT+ENTER
|
||||||
hotkeys.toggleFastForward.raw = xml_hotkeys.get("ToggleFastforward", WXK_NONE);
|
hotkeys.takeScreenshot = xml_hotkeys.get("TakeScreenshot", sHotkeyCfg{uKeyboardHotkey{WXK_F12}});
|
||||||
|
hotkeys.toggleFastForward = xml_hotkeys.get("ToggleFastForward", sHotkeyCfg{});
|
||||||
|
|
||||||
// emulatedusbdevices
|
// emulatedusbdevices
|
||||||
auto usbdevices = parser.get("EmulatedUsbDevices");
|
auto usbdevices = parser.get("EmulatedUsbDevices");
|
||||||
|
@ -554,11 +555,12 @@ void CemuConfig::Save(XMLConfigParser& parser)
|
||||||
|
|
||||||
// hotkeys
|
// hotkeys
|
||||||
auto xml_hotkeys = config.set("Hotkeys");
|
auto xml_hotkeys = config.set("Hotkeys");
|
||||||
xml_hotkeys.set("ExitFullscreen", hotkeys.exitFullscreen.raw);
|
xml_hotkeys.set("modifiers", hotkeys.modifiers);
|
||||||
xml_hotkeys.set("ToggleFullscreen", hotkeys.toggleFullscreen.raw);
|
xml_hotkeys.set("ExitFullscreen", hotkeys.exitFullscreen);
|
||||||
xml_hotkeys.set("ToggleFullscreenAlt", hotkeys.toggleFullscreenAlt.raw);
|
xml_hotkeys.set("ToggleFullscreen", hotkeys.toggleFullscreen);
|
||||||
xml_hotkeys.set("TakeScreenshot", hotkeys.takeScreenshot.raw);
|
xml_hotkeys.set("ToggleFullscreenAlt", hotkeys.toggleFullscreenAlt);
|
||||||
xml_hotkeys.set("ToggleFastForward", hotkeys.toggleFastForward.raw);
|
xml_hotkeys.set("TakeScreenshot", hotkeys.takeScreenshot);
|
||||||
|
xml_hotkeys.set("ToggleFastForward", hotkeys.toggleFastForward);
|
||||||
|
|
||||||
// emulated usb devices
|
// emulated usb devices
|
||||||
auto usbdevices = config.set("EmulatedUsbDevices");
|
auto usbdevices = config.set("EmulatedUsbDevices");
|
||||||
|
|
|
@ -195,13 +195,42 @@ typedef union
|
||||||
{
|
{
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
uint16_t key : 13; // enough bits for all keycodes
|
uint16 key : 13; // enough bits for all keycodes
|
||||||
uint16_t alt : 1;
|
uint16 alt : 1;
|
||||||
uint16_t ctrl : 1;
|
uint16 ctrl : 1;
|
||||||
uint16_t shift : 1;
|
uint16 shift : 1;
|
||||||
|
};
|
||||||
|
uint16 raw;
|
||||||
|
} uKeyboardHotkey;
|
||||||
|
|
||||||
|
typedef sint16 ControllerHotkey_t;
|
||||||
|
|
||||||
|
struct sHotkeyCfg
|
||||||
|
{
|
||||||
|
uKeyboardHotkey keyboard{WXK_NONE};
|
||||||
|
ControllerHotkey_t controller{-1}; // -1 = disabled
|
||||||
|
|
||||||
|
/* for defaults */
|
||||||
|
sHotkeyCfg(const uKeyboardHotkey& keyboard = {WXK_NONE}, const ControllerHotkey_t& controller = {-1}) :
|
||||||
|
keyboard(keyboard), controller(controller) {};
|
||||||
|
|
||||||
|
/* for reading from xml */
|
||||||
|
sHotkeyCfg(const char* xml_values)
|
||||||
|
{
|
||||||
|
std::istringstream iss(xml_values);
|
||||||
|
iss >> keyboard.raw >> controller;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<sHotkeyCfg> : formatter<string_view>
|
||||||
|
{
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const sHotkeyCfg c, FormatContext &ctx) const {
|
||||||
|
std::string xml_values = fmt::format("{} {}", c.keyboard.raw, c.controller);
|
||||||
|
return formatter<string_view>::format(xml_values, ctx);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
uint16_t raw;
|
|
||||||
} uHotkey;
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> {
|
struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> {
|
||||||
|
@ -514,11 +543,12 @@ struct CemuConfig
|
||||||
// hotkeys
|
// hotkeys
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
uHotkey toggleFullscreen{};
|
sHotkeyCfg modifiers;
|
||||||
uHotkey toggleFullscreenAlt{};
|
sHotkeyCfg toggleFullscreen;
|
||||||
uHotkey exitFullscreen{};
|
sHotkeyCfg toggleFullscreenAlt;
|
||||||
uHotkey takeScreenshot{};
|
sHotkeyCfg exitFullscreen;
|
||||||
uHotkey toggleFastForward{};
|
sHotkeyCfg takeScreenshot;
|
||||||
|
sHotkeyCfg toggleFastForward;
|
||||||
} hotkeys{};
|
} hotkeys{};
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept HasConstCharConstructor = requires { T(std::declval<const char*>()); };
|
||||||
|
|
||||||
class XMLConfigParser
|
class XMLConfigParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -43,7 +46,7 @@ public:
|
||||||
return element->Int64Text(default_value);
|
return element->Int64Text(default_value);
|
||||||
else if constexpr (std::is_same_v<T, uint64>) // doesnt support real uint64...
|
else if constexpr (std::is_same_v<T, uint64>) // doesnt support real uint64...
|
||||||
return (uint64)element->Int64Text((sint64)default_value);
|
return (uint64)element->Int64Text((sint64)default_value);
|
||||||
else if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, std::string>)
|
else if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, std::string> || HasConstCharConstructor<T>)
|
||||||
{
|
{
|
||||||
const char* text = element->GetText();
|
const char* text = element->GetText();
|
||||||
return text ? text : default_value;
|
return text ? text : default_value;
|
||||||
|
|
|
@ -1446,14 +1446,14 @@ void MainWindow::OnKeyUp(wxKeyEvent& event)
|
||||||
if (swkbd_hasKeyboardInputHook())
|
if (swkbd_hasKeyboardInputHook())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uHotkey hotkey{};
|
uKeyboardHotkey hotkey{};
|
||||||
hotkey.key = event.GetKeyCode();
|
hotkey.key = event.GetKeyCode();
|
||||||
hotkey.alt = event.AltDown();
|
hotkey.alt = event.AltDown();
|
||||||
hotkey.ctrl = event.ControlDown();
|
hotkey.ctrl = event.ControlDown();
|
||||||
hotkey.shift = event.ShiftDown();
|
hotkey.shift = event.ShiftDown();
|
||||||
const auto& hotkeyMap = HotkeySettings::s_hotkeyToFuncMap;
|
const auto& hotkey_map = HotkeySettings::s_keyboardHotkeyToFuncMap;
|
||||||
const auto it = hotkeyMap.find(hotkey.raw);
|
const auto it = hotkey_map.find(hotkey.raw);
|
||||||
if (it != hotkeyMap.end())
|
if (it != hotkey_map.end())
|
||||||
it->second();
|
it->second();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "gui/input/HotkeySettings.h"
|
#include "gui/input/HotkeySettings.h"
|
||||||
#include <config/ActiveSettings.h>
|
#include <config/ActiveSettings.h>
|
||||||
#include <gui/guiWrapper.h>
|
#include <gui/guiWrapper.h>
|
||||||
|
#include "input/InputManager.h"
|
||||||
|
#include "HotkeySettings.h"
|
||||||
|
|
||||||
extern WindowInfo g_window_info;
|
extern WindowInfo g_window_info;
|
||||||
const std::unordered_map<uHotkey*, std::function<void(void)>> HotkeySettings::s_cfgHotkeyToFuncMap{
|
const std::unordered_map<sHotkeyCfg*, std::function<void(void)>> HotkeySettings::s_cfgHotkeyToFuncMap{
|
||||||
{&s_cfgHotkeys.toggleFullscreen, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }},
|
{&s_cfgHotkeys.toggleFullscreen, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }},
|
||||||
{&s_cfgHotkeys.toggleFullscreenAlt, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }},
|
{&s_cfgHotkeys.toggleFullscreenAlt, [](void) { s_mainWindow->ShowFullScreen(!s_mainWindow->IsFullScreen()); }},
|
||||||
{&s_cfgHotkeys.exitFullscreen, [](void) { s_mainWindow->ShowFullScreen(false); }},
|
{&s_cfgHotkeys.exitFullscreen, [](void) { s_mainWindow->ShowFullScreen(false); }},
|
||||||
|
@ -13,35 +15,61 @@ const std::unordered_map<uHotkey*, std::function<void(void)>> HotkeySettings::s_
|
||||||
|
|
||||||
struct HotkeyEntry
|
struct HotkeyEntry
|
||||||
{
|
{
|
||||||
|
enum class InputButtonType : wxWindowID {
|
||||||
|
Keyboard,
|
||||||
|
Controller,
|
||||||
|
};
|
||||||
|
|
||||||
std::unique_ptr<wxStaticText> name;
|
std::unique_ptr<wxStaticText> name;
|
||||||
std::unique_ptr<wxButton> keyInput;
|
std::unique_ptr<wxButton> keyInput;
|
||||||
uHotkey& hotkey;
|
std::unique_ptr<wxButton> controllerInput;
|
||||||
|
sHotkeyCfg& hotkey;
|
||||||
|
|
||||||
HotkeyEntry(wxStaticText* name, wxButton* keyInput, uHotkey& hotkey)
|
HotkeyEntry(wxStaticText* name, wxButton* keyInput, wxButton* controllerInput, sHotkeyCfg& hotkey)
|
||||||
: name(name), keyInput(keyInput), hotkey(hotkey)
|
: name(name), keyInput(keyInput), controllerInput(controllerInput), hotkey(hotkey)
|
||||||
{
|
{
|
||||||
keyInput->SetClientData(&hotkey);
|
keyInput->SetClientData(&hotkey);
|
||||||
|
keyInput->SetId(static_cast<wxWindowID>(InputButtonType::Keyboard));
|
||||||
|
controllerInput->SetClientData(&hotkey);
|
||||||
|
controllerInput->SetId(static_cast<wxWindowID>(InputButtonType::Controller));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
HotkeySettings::HotkeySettings(wxWindow* parent)
|
HotkeySettings::HotkeySettings(wxWindow* parent)
|
||||||
: wxFrame(parent, wxID_ANY, "Hotkey Settings")
|
: wxFrame(parent, wxID_ANY, "Hotkey Settings")
|
||||||
{
|
{
|
||||||
m_panel = new wxPanel(this);
|
SetIcon(wxICON(X_HOTKEY_SETTINGS));
|
||||||
m_sizer = new wxFlexGridSizer(0, 2, wxSize(0, 0));
|
|
||||||
|
|
||||||
|
m_sizer = new wxFlexGridSizer(0, 3, 10, 10);
|
||||||
|
m_sizer->AddGrowableCol(1);
|
||||||
|
m_sizer->AddGrowableCol(2);
|
||||||
|
|
||||||
|
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE);
|
||||||
m_panel->SetSizer(m_sizer);
|
m_panel->SetSizer(m_sizer);
|
||||||
Center();
|
Center();
|
||||||
|
|
||||||
CreateHotkey("Toggle fullscreen", s_cfgHotkeys.toggleFullscreen);
|
SetActiveController();
|
||||||
CreateHotkey("Take screenshot", s_cfgHotkeys.takeScreenshot);
|
|
||||||
CreateHotkey("Toggle fast-forward", s_cfgHotkeys.toggleFastForward);
|
CreateColumnHeaders();
|
||||||
|
|
||||||
|
/* global modifier */
|
||||||
|
CreateHotkeyRow("Hotkey modifier", s_cfgHotkeys.modifiers);
|
||||||
|
m_hotkeys.at(0).keyInput->Hide();
|
||||||
|
|
||||||
|
/* hotkeys */
|
||||||
|
CreateHotkeyRow("Toggle fullscreen", s_cfgHotkeys.toggleFullscreen);
|
||||||
|
CreateHotkeyRow("Take screenshot", s_cfgHotkeys.takeScreenshot);
|
||||||
|
CreateHotkeyRow("Toggle fast-forward", s_cfgHotkeys.toggleFastForward);
|
||||||
|
|
||||||
|
m_controllerTimer = new wxTimer(this);
|
||||||
|
Bind(wxEVT_TIMER, &HotkeySettings::OnControllerTimer, this);
|
||||||
|
|
||||||
m_sizer->SetSizeHints(this);
|
m_sizer->SetSizeHints(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
HotkeySettings::~HotkeySettings()
|
HotkeySettings::~HotkeySettings()
|
||||||
{
|
{
|
||||||
|
m_controllerTimer->Stop();
|
||||||
if (m_needToSave)
|
if (m_needToSave)
|
||||||
{
|
{
|
||||||
g_config.Save();
|
g_config.Save();
|
||||||
|
@ -50,100 +78,232 @@ HotkeySettings::~HotkeySettings()
|
||||||
|
|
||||||
void HotkeySettings::Init(wxFrame* mainWindowFrame)
|
void HotkeySettings::Init(wxFrame* mainWindowFrame)
|
||||||
{
|
{
|
||||||
s_hotkeyToFuncMap.reserve(s_cfgHotkeyToFuncMap.size());
|
s_keyboardHotkeyToFuncMap.reserve(s_cfgHotkeyToFuncMap.size());
|
||||||
for (const auto& [cfgHotkey, func] : s_cfgHotkeyToFuncMap)
|
for (const auto& [cfgHotkey, func] : s_cfgHotkeyToFuncMap)
|
||||||
{
|
{
|
||||||
auto hotkeyRaw = cfgHotkey->raw;
|
auto keyboardHotkey = cfgHotkey->keyboard.raw;
|
||||||
if (hotkeyRaw > 0)
|
if (keyboardHotkey > 0)
|
||||||
{
|
{
|
||||||
s_hotkeyToFuncMap[hotkeyRaw] = func;
|
s_keyboardHotkeyToFuncMap[keyboardHotkey] = func;
|
||||||
|
}
|
||||||
|
auto controllerHotkey = cfgHotkey->controller;
|
||||||
|
if (controllerHotkey > 0)
|
||||||
|
{
|
||||||
|
s_controllerHotkeyToFuncMap[controllerHotkey] = func;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s_mainWindow = mainWindowFrame;
|
s_mainWindow = mainWindowFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::CreateHotkey(const wxString& label, uHotkey& hotkey)
|
void HotkeySettings::CreateColumnHeaders(void)
|
||||||
{
|
{
|
||||||
/* add new hotkey */
|
auto* emptySpace = new wxStaticText(m_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
|
||||||
{
|
auto* keyboard = new wxStaticText(m_panel, wxID_ANY, "Keyboard", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
|
||||||
auto* name = new wxStaticText(m_panel, wxID_ANY, label, wxDefaultPosition, wxSize(240, 30), wxALIGN_CENTER);
|
auto* controller = new wxStaticText(m_panel, wxID_ANY, "Controller", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
|
||||||
auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(hotkey), wxDefaultPosition, wxSize(120, 30), wxWANTS_CHARS);
|
|
||||||
|
|
||||||
keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnHotkeyInputLeftClick, this);
|
keyboard->SetMinSize(m_minButtonSize);
|
||||||
|
controller->SetMinSize(m_minButtonSize);
|
||||||
|
|
||||||
|
auto flags = wxSizerFlags().Expand();
|
||||||
|
m_sizer->Add(emptySpace, flags);
|
||||||
|
m_sizer->Add(keyboard, flags);
|
||||||
|
m_sizer->Add(controller, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HotkeySettings::CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey)
|
||||||
|
{
|
||||||
|
auto* name = new wxStaticText(m_panel, wxID_ANY, label);
|
||||||
|
auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.keyboard), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT);
|
||||||
|
auto* controllerInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.controller), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT);
|
||||||
|
|
||||||
|
/* for starting input */
|
||||||
|
keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnKeyboardHotkeyInputLeftClick, this);
|
||||||
|
controllerInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnControllerHotkeyInputLeftClick, this);
|
||||||
|
|
||||||
|
/* for cancelling and clearing input */
|
||||||
keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this);
|
keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this);
|
||||||
|
controllerInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this);
|
||||||
|
|
||||||
|
keyInput->SetMinSize(m_minButtonSize);
|
||||||
|
controllerInput->SetMinSize(m_minButtonSize);
|
||||||
|
|
||||||
auto flags = wxSizerFlags().Expand();
|
auto flags = wxSizerFlags().Expand();
|
||||||
m_sizer->Add(name, flags);
|
m_sizer->Add(name, flags);
|
||||||
m_sizer->Add(keyInput, flags);
|
m_sizer->Add(keyInput, flags);
|
||||||
|
m_sizer->Add(controllerInput, flags);
|
||||||
|
|
||||||
m_hotkeys.emplace_back(name, keyInput, hotkey);
|
m_hotkeys.emplace_back(name, keyInput, controllerInput, cfgHotkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HotkeySettings::OnControllerTimer(wxTimerEvent& event)
|
||||||
|
{
|
||||||
|
if (m_activeController.expired())
|
||||||
|
{
|
||||||
|
m_controllerTimer->Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& controller = *m_activeController.lock();
|
||||||
|
auto buttons = controller.update_state().buttons;
|
||||||
|
if (!buttons.IsIdle())
|
||||||
|
{
|
||||||
|
for (const auto& newHotkey : buttons.GetButtonList())
|
||||||
|
{
|
||||||
|
m_controllerTimer->Stop();
|
||||||
|
auto* inputButton = static_cast<wxButton*>(m_controllerTimer->GetClientData());
|
||||||
|
auto& cfgHotkey = *static_cast<sHotkeyCfg*>(inputButton->GetClientData());
|
||||||
|
auto oldHotkey = cfgHotkey.controller;
|
||||||
|
cfgHotkey.controller = newHotkey;
|
||||||
|
/* don't bind modifier to map */
|
||||||
|
if (&cfgHotkey != &s_cfgHotkeys.modifiers)
|
||||||
|
{
|
||||||
|
s_controllerHotkeyToFuncMap.erase(oldHotkey);
|
||||||
|
s_controllerHotkeyToFuncMap[newHotkey] = s_cfgHotkeyToFuncMap.at(&cfgHotkey);
|
||||||
|
}
|
||||||
|
m_needToSave |= true;
|
||||||
|
FinalizeInput<ControllerHotkey_t>(inputButton);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::OnHotkeyInputLeftClick(wxCommandEvent& event)
|
void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
||||||
if (m_activeInputButton)
|
if (m_activeInputButton)
|
||||||
{
|
{
|
||||||
/* ignore multiple clicks of the same button */
|
/* ignore multiple clicks of the same button */
|
||||||
if (inputButton == m_activeInputButton) return;
|
if (inputButton == m_activeInputButton) return;
|
||||||
FinalizeInput(m_activeInputButton);
|
RestoreInputButton(m_activeInputButton);
|
||||||
}
|
}
|
||||||
inputButton->Bind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this);
|
inputButton->Bind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this);
|
||||||
inputButton->SetLabelText('_');
|
inputButton->SetLabelText('_');
|
||||||
m_activeInputButton = inputButton;
|
m_activeInputButton = inputButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::OnHotkeyInputRightClick(wxMouseEvent& event) {
|
void HotkeySettings::OnControllerHotkeyInputLeftClick(wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
||||||
if (m_activeInputButton)
|
if (m_activeInputButton)
|
||||||
{
|
{
|
||||||
FinalizeInput(m_activeInputButton);
|
/* ignore multiple clicks of the same button */
|
||||||
|
if (inputButton == m_activeInputButton) return;
|
||||||
|
RestoreInputButton(m_activeInputButton);
|
||||||
|
}
|
||||||
|
m_controllerTimer->Stop();
|
||||||
|
if (!SetActiveController())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inputButton->SetLabelText('_');
|
||||||
|
m_controllerTimer->SetClientData(inputButton);
|
||||||
|
m_controllerTimer->Start(25);
|
||||||
|
m_activeInputButton = inputButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HotkeySettings::OnHotkeyInputRightClick(wxMouseEvent& event)
|
||||||
|
{
|
||||||
|
if (m_activeInputButton)
|
||||||
|
{
|
||||||
|
RestoreInputButton(m_activeInputButton);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
||||||
auto& cfgHotkey = *static_cast<uHotkey*>(inputButton->GetClientData());
|
auto& cfgHotkey = *static_cast<sHotkeyCfg*>(inputButton->GetClientData());
|
||||||
uHotkey newHotkey{};
|
using InputButtonType = HotkeyEntry::InputButtonType;
|
||||||
if (cfgHotkey.raw != newHotkey.raw)
|
switch (static_cast<InputButtonType>(inputButton->GetId()))
|
||||||
|
{
|
||||||
|
case InputButtonType::Keyboard: {
|
||||||
|
uKeyboardHotkey newHotkey{};
|
||||||
|
if (cfgHotkey.keyboard.raw != newHotkey.raw)
|
||||||
{
|
{
|
||||||
m_needToSave |= true;
|
m_needToSave |= true;
|
||||||
s_hotkeyToFuncMap.erase(cfgHotkey.raw);
|
s_keyboardHotkeyToFuncMap.erase(cfgHotkey.keyboard.raw);
|
||||||
cfgHotkey = newHotkey;
|
cfgHotkey.keyboard = newHotkey;
|
||||||
|
FinalizeInput<uKeyboardHotkey>(inputButton);
|
||||||
}
|
}
|
||||||
FinalizeInput(inputButton);
|
} break;
|
||||||
|
case InputButtonType::Controller: {
|
||||||
|
ControllerHotkey_t newHotkey{ -1 };
|
||||||
|
if (cfgHotkey.controller != newHotkey)
|
||||||
|
{
|
||||||
|
m_needToSave |= true;
|
||||||
|
s_controllerHotkeyToFuncMap.erase(cfgHotkey.controller);
|
||||||
|
cfgHotkey.controller = newHotkey;
|
||||||
|
FinalizeInput<ControllerHotkey_t>(inputButton);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HotkeySettings::SetActiveController(void)
|
||||||
|
{
|
||||||
|
auto emulated_controller = InputManager::instance().get_controller(0);
|
||||||
|
if (emulated_controller.use_count() <= 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& controllers = emulated_controller->get_controllers();
|
||||||
|
if (controllers.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_activeController = controllers.at(0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::OnKeyUp(wxKeyEvent& event)
|
void HotkeySettings::OnKeyUp(wxKeyEvent& event)
|
||||||
{
|
{
|
||||||
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
auto* inputButton = static_cast<wxButton*>(event.GetEventObject());
|
||||||
auto& cfgHotkey = *static_cast<uHotkey*>(inputButton->GetClientData());
|
auto& cfgHotkey = *static_cast<sHotkeyCfg*>(inputButton->GetClientData());
|
||||||
if (auto keycode = event.GetKeyCode(); IsValidKeycodeUp(keycode))
|
if (auto keycode = event.GetKeyCode(); IsValidKeycodeUp(keycode))
|
||||||
{
|
{
|
||||||
auto oldHotkey = cfgHotkey;
|
auto oldHotkey = cfgHotkey.keyboard;
|
||||||
uHotkey newHotkey{};
|
uKeyboardHotkey newHotkey{};
|
||||||
newHotkey.key = keycode;
|
newHotkey.key = keycode;
|
||||||
newHotkey.alt = event.AltDown();
|
newHotkey.alt = event.AltDown();
|
||||||
newHotkey.ctrl = event.ControlDown();
|
newHotkey.ctrl = event.ControlDown();
|
||||||
newHotkey.shift = event.ShiftDown();
|
newHotkey.shift = event.ShiftDown();
|
||||||
if ((newHotkey.raw != oldHotkey.raw) &&
|
if ((newHotkey.raw != oldHotkey.raw) &&
|
||||||
(s_hotkeyToFuncMap.find(newHotkey.raw) == s_hotkeyToFuncMap.end()))
|
(s_keyboardHotkeyToFuncMap.find(newHotkey.raw) == s_keyboardHotkeyToFuncMap.end()))
|
||||||
{
|
{
|
||||||
m_needToSave |= true;
|
m_needToSave |= true;
|
||||||
cfgHotkey = newHotkey;
|
cfgHotkey.keyboard = newHotkey;
|
||||||
s_hotkeyToFuncMap.erase(oldHotkey.raw);
|
s_keyboardHotkeyToFuncMap.erase(oldHotkey.raw);
|
||||||
s_hotkeyToFuncMap[cfgHotkey.raw] = s_cfgHotkeyToFuncMap.at(&cfgHotkey);
|
s_keyboardHotkeyToFuncMap[newHotkey.raw] = s_cfgHotkeyToFuncMap.at(&cfgHotkey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FinalizeInput(inputButton);
|
FinalizeInput<uKeyboardHotkey>(inputButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
void HotkeySettings::FinalizeInput(wxButton* inputButton)
|
void HotkeySettings::FinalizeInput(wxButton* inputButton)
|
||||||
{
|
{
|
||||||
auto& cfgHotkey = *static_cast<uHotkey*>(inputButton->GetClientData());
|
auto& cfgHotkey = *static_cast<sHotkeyCfg*>(inputButton->GetClientData());
|
||||||
|
if constexpr (std::is_same_v<T, uKeyboardHotkey>)
|
||||||
|
{
|
||||||
inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this);
|
inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this);
|
||||||
inputButton->SetLabelText(To_wxString(cfgHotkey));
|
inputButton->SetLabelText(To_wxString(cfgHotkey.keyboard));
|
||||||
|
} else if constexpr (std::is_same_v<T, ControllerHotkey_t>)
|
||||||
|
{
|
||||||
|
inputButton->SetLabelText(To_wxString(cfgHotkey.controller));
|
||||||
|
}
|
||||||
m_activeInputButton = nullptr;
|
m_activeInputButton = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HotkeySettings::RestoreInputButton(wxButton* inputButton)
|
||||||
|
{
|
||||||
|
using InputButtonType = HotkeyEntry::InputButtonType;
|
||||||
|
switch (static_cast<InputButtonType>(inputButton->GetId()))
|
||||||
|
{
|
||||||
|
case InputButtonType::Keyboard: {
|
||||||
|
FinalizeInput<uKeyboardHotkey>(inputButton);
|
||||||
|
} break;
|
||||||
|
case InputButtonType::Controller: {
|
||||||
|
FinalizeInput<ControllerHotkey_t>(inputButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HotkeySettings::IsValidKeycodeUp(int keycode)
|
bool HotkeySettings::IsValidKeycodeUp(int keycode)
|
||||||
{
|
{
|
||||||
switch (keycode)
|
switch (keycode)
|
||||||
|
@ -159,13 +319,11 @@ bool HotkeySettings::IsValidKeycodeUp(int keycode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString HotkeySettings::To_wxString(uHotkey hotkey)
|
wxString HotkeySettings::To_wxString(uKeyboardHotkey hotkey)
|
||||||
{
|
{
|
||||||
if (hotkey.raw <= 0)
|
wxString ret_val{};
|
||||||
|
if (hotkey.raw)
|
||||||
{
|
{
|
||||||
return "";
|
|
||||||
}
|
|
||||||
wxString ret_val;
|
|
||||||
if (hotkey.alt)
|
if (hotkey.alt)
|
||||||
{
|
{
|
||||||
ret_val.append("ALT + ");
|
ret_val.append("ALT + ");
|
||||||
|
@ -179,5 +337,16 @@ wxString HotkeySettings::To_wxString(uHotkey hotkey)
|
||||||
ret_val.append("SHIFT + ");
|
ret_val.append("SHIFT + ");
|
||||||
}
|
}
|
||||||
ret_val.append(wxAcceleratorEntry(0, hotkey.key).ToString());
|
ret_val.append(wxAcceleratorEntry(0, hotkey.key).ToString());
|
||||||
|
}
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString HotkeySettings::To_wxString(ControllerHotkey_t hotkey)
|
||||||
|
{
|
||||||
|
wxString ret_val{};
|
||||||
|
if ((hotkey != -1) && !m_activeController.expired())
|
||||||
|
{
|
||||||
|
ret_val = m_activeController.lock()->get_button_name(hotkey);
|
||||||
|
}
|
||||||
return ret_val;
|
return ret_val;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
#include "config/CemuConfig.h"
|
#include "config/CemuConfig.h"
|
||||||
|
#include "input/api/Controller.h"
|
||||||
|
|
||||||
class HotkeyEntry;
|
class HotkeyEntry;
|
||||||
|
|
||||||
|
@ -9,30 +10,43 @@ class HotkeySettings : public wxFrame
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void Init(wxFrame* mainWindowFrame);
|
static void Init(wxFrame* mainWindowFrame);
|
||||||
inline static std::unordered_map<int, std::function<void(void)>> s_hotkeyToFuncMap{};
|
inline static std::unordered_map<uint16, std::function<void(void)>> s_keyboardHotkeyToFuncMap{};
|
||||||
|
inline static std::unordered_map<uint16, std::function<void(void)>> s_controllerHotkeyToFuncMap{};
|
||||||
|
inline static auto& s_cfgHotkeys = GetConfig().hotkeys;
|
||||||
|
|
||||||
HotkeySettings(wxWindow* parent);
|
HotkeySettings(wxWindow* parent);
|
||||||
~HotkeySettings();
|
~HotkeySettings();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline static wxFrame* s_mainWindow = nullptr;
|
inline static wxFrame* s_mainWindow = nullptr;
|
||||||
inline static auto& s_cfgHotkeys = GetConfig().hotkeys;
|
static const std::unordered_map<sHotkeyCfg*, std::function<void(void)>> s_cfgHotkeyToFuncMap;
|
||||||
static const std::unordered_map<uHotkey*, std::function<void(void)>> s_cfgHotkeyToFuncMap;
|
|
||||||
|
|
||||||
wxPanel* m_panel;
|
wxPanel* m_panel;
|
||||||
wxFlexGridSizer* m_sizer;
|
wxFlexGridSizer* m_sizer;
|
||||||
|
wxButton* m_activeInputButton{ nullptr };
|
||||||
|
wxTimer* m_controllerTimer{ nullptr };
|
||||||
|
const wxSize m_minButtonSize{ 200, 30 };
|
||||||
|
|
||||||
std::vector<HotkeyEntry> m_hotkeys;
|
std::vector<HotkeyEntry> m_hotkeys;
|
||||||
wxButton* m_activeInputButton = nullptr;
|
std::weak_ptr<ControllerBase> m_activeController{};
|
||||||
bool m_needToSave = false;
|
bool m_needToSave = false;
|
||||||
|
|
||||||
/* helpers */
|
/* helpers */
|
||||||
void CreateHotkey(const wxString& label, uHotkey& hotkey);
|
void CreateColumnHeaders(void);
|
||||||
wxString To_wxString(uHotkey hotkey);
|
void CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey);
|
||||||
|
wxString To_wxString(uKeyboardHotkey hotkey);
|
||||||
|
wxString To_wxString(ControllerHotkey_t hotkey);
|
||||||
bool IsValidKeycodeUp(int keycode);
|
bool IsValidKeycodeUp(int keycode);
|
||||||
|
bool SetActiveController(void);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
void FinalizeInput(wxButton* inputButton);
|
void FinalizeInput(wxButton* inputButton);
|
||||||
|
void RestoreInputButton(wxButton* inputButton);
|
||||||
|
|
||||||
/* events */
|
/* events */
|
||||||
void OnHotkeyInputLeftClick(wxCommandEvent& event);
|
void OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event);
|
||||||
|
void OnControllerHotkeyInputLeftClick(wxCommandEvent& event);
|
||||||
void OnHotkeyInputRightClick(wxMouseEvent& event);
|
void OnHotkeyInputRightClick(wxMouseEvent& event);
|
||||||
void OnKeyUp(wxKeyEvent& event);
|
void OnKeyUp(wxKeyEvent& event);
|
||||||
|
void OnControllerTimer(wxTimerEvent& event);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "input/api/Controller.h"
|
#include "input/api/Controller.h"
|
||||||
|
#include "config/CemuConfig.h"
|
||||||
|
#include "gui/input/HotkeySettings.h"
|
||||||
|
|
||||||
#include "gui/guiWrapper.h"
|
#include "gui/guiWrapper.h"
|
||||||
|
|
||||||
|
@ -67,6 +69,22 @@ const ControllerState& ControllerBase::update_state()
|
||||||
|
|
||||||
#undef APPLY_AXIS_BUTTON
|
#undef APPLY_AXIS_BUTTON
|
||||||
|
|
||||||
|
/* hotkey capturing */
|
||||||
|
const auto& hotkey_mod = HotkeySettings::s_cfgHotkeys.modifiers.controller;
|
||||||
|
if ((hotkey_mod >= 0) && result.buttons.GetButtonState(hotkey_mod)) {
|
||||||
|
const auto& hotkey_map = HotkeySettings::s_controllerHotkeyToFuncMap;
|
||||||
|
for (const auto& button_id : result.buttons.GetButtonList()) {
|
||||||
|
const auto it = hotkey_map.find(button_id);
|
||||||
|
if (it == hotkey_map.end())
|
||||||
|
continue;
|
||||||
|
/* only capture clicks */
|
||||||
|
if (m_last_state.buttons.GetButtonState(button_id))
|
||||||
|
break;
|
||||||
|
it->second();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_last_state = std::move(result);
|
m_last_state = std::move(result);
|
||||||
return m_last_state;
|
return m_last_state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ X_SETTINGS ICON "resource\\icons8_automatic_26_x
|
||||||
|
|
||||||
X_GAME_PROFILE ICON "resource\\icons8-compose-filled-50.ico"
|
X_GAME_PROFILE ICON "resource\\icons8-compose-filled-50.ico"
|
||||||
|
|
||||||
|
X_HOTKEY_SETTINGS ICON "resource\\icons8_hotkeys.ico"
|
||||||
|
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
70
src/resource/embedded/X_HOTKEY_SETTINGS.xpm
Normal file
70
src/resource/embedded/X_HOTKEY_SETTINGS.xpm
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/* XPM */
|
||||||
|
const char *X_HOTKEY_SETTINGS[] = {
|
||||||
|
"64 64 2 1",
|
||||||
|
" c None",
|
||||||
|
". c #000000",
|
||||||
|
" ... ",
|
||||||
|
" .... ",
|
||||||
|
" .... ",
|
||||||
|
" .. .... ",
|
||||||
|
" .. ...... ",
|
||||||
|
" ... ...... ",
|
||||||
|
" ... ...... ",
|
||||||
|
" ... ....... ",
|
||||||
|
" .... ........ ... ",
|
||||||
|
" .... ......... ... ",
|
||||||
|
" .... ......... ... ",
|
||||||
|
" .... .......... .... ",
|
||||||
|
" .... ......... .... ",
|
||||||
|
" ..... ........... ..... ",
|
||||||
|
" ..... .......... ..... ",
|
||||||
|
" ........ ........... ...... ",
|
||||||
|
" ........ ... ....... ...... ",
|
||||||
|
" ........... ... ....... ...... ",
|
||||||
|
" ........... ..... ...... ....... ",
|
||||||
|
" ........... ..... ...... ........ ",
|
||||||
|
" ............. ...... ...... ......... ",
|
||||||
|
" ........ .... ...... .... ..... .... ",
|
||||||
|
" ........ .... ....... .... ...... .... ",
|
||||||
|
" ...... .... ...... ..... ....... ..... ",
|
||||||
|
" ...... ........... ............ .... ",
|
||||||
|
" ...... .......... .......... .... ",
|
||||||
|
" ..... .......... .......... .... ",
|
||||||
|
" ..... .......... ......... ..... ",
|
||||||
|
" ..... .......... ........ ..... ",
|
||||||
|
" ..... ........ ........ ...... ",
|
||||||
|
" ..... ..... ..... ..... ",
|
||||||
|
" ..... ..... .... ...... ",
|
||||||
|
" ..... ..... ",
|
||||||
|
" ... .... ",
|
||||||
|
" . ............................ .. ",
|
||||||
|
" ................................ ",
|
||||||
|
" ................................ ",
|
||||||
|
" .................................... ",
|
||||||
|
" .................................... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... .......... ",
|
||||||
|
" .......... .......... ",
|
||||||
|
" .......... .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .......... ........ .......... ",
|
||||||
|
" .................................... ",
|
||||||
|
" .................................... ",
|
||||||
|
" .................................... ",
|
||||||
|
" .................................... ",
|
||||||
|
" ................................ ",
|
||||||
|
" ................................ ",
|
||||||
|
" ............................. "
|
||||||
|
};
|
|
@ -2,6 +2,7 @@
|
||||||
#include "M_WND_ICON128.xpm"
|
#include "M_WND_ICON128.xpm"
|
||||||
#include "X_BOX.xpm"
|
#include "X_BOX.xpm"
|
||||||
#include "X_SETTINGS.xpm"
|
#include "X_SETTINGS.xpm"
|
||||||
|
#include "X_HOTKEY_SETTINGS.xpm"
|
||||||
|
|
||||||
#include "icons8-checkmark-yes-32.hpng"
|
#include "icons8-checkmark-yes-32.hpng"
|
||||||
#include "icons8-error-32.hpng"
|
#include "icons8-error-32.hpng"
|
||||||
|
|
|
@ -2,6 +2,7 @@ extern const char* X_GAME_PROFILE_xpm[];
|
||||||
extern const char* M_WND_ICON128_xpm[];
|
extern const char* M_WND_ICON128_xpm[];
|
||||||
extern const char* X_BOX_xpm[];
|
extern const char* X_BOX_xpm[];
|
||||||
extern const char* X_SETTINGS_xpm[];
|
extern const char* X_SETTINGS_xpm[];
|
||||||
|
extern const char* X_HOTKEY_SETTINGS_xpm[];
|
||||||
|
|
||||||
extern unsigned char PNG_CHECK_YES_png[573];
|
extern unsigned char PNG_CHECK_YES_png[573];
|
||||||
extern unsigned char PNG_ERROR_png[472];
|
extern unsigned char PNG_ERROR_png[472];
|
||||||
|
|
BIN
src/resource/icons8_hotkeys.ico
Normal file
BIN
src/resource/icons8_hotkeys.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
src/resource/icons8_hotkeys.png
Normal file
BIN
src/resource/icons8_hotkeys.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 506 B |
Loading…
Add table
Add a link
Reference in a new issue