input: controller support for configurable hotkeys

This commit is contained in:
Anime 2025-03-25 17:59:42 +02:00
parent dc2d0901e6
commit c8f5c2c528
13 changed files with 407 additions and 98 deletions

View file

@ -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");

View file

@ -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_t raw; uint16 raw;
} uHotkey; } 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);
}
};
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

View file

@ -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;

View file

@ -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();
} }

View file

@ -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* controller = new wxStaticText(m_panel, wxID_ANY, "Controller", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
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);
controllerInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this);
keyInput->SetMinSize(m_minButtonSize);
controllerInput->SetMinSize(m_minButtonSize);
auto flags = wxSizerFlags().Expand();
m_sizer->Add(name, flags);
m_sizer->Add(keyInput, flags);
m_sizer->Add(controllerInput, flags);
m_hotkeys.emplace_back(name, keyInput, controllerInput, cfgHotkey);
}
void HotkeySettings::OnControllerTimer(wxTimerEvent& event)
{
if (m_activeController.expired())
{ {
auto* name = new wxStaticText(m_panel, wxID_ANY, label, wxDefaultPosition, wxSize(240, 30), wxALIGN_CENTER); m_controllerTimer->Stop();
auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(hotkey), wxDefaultPosition, wxSize(120, 30), wxWANTS_CHARS); return;
}
keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnHotkeyInputLeftClick, this); auto& controller = *m_activeController.lock();
keyInput->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(HotkeySettings::OnHotkeyInputRightClick), NULL, this); auto buttons = controller.update_state().buttons;
if (!buttons.IsIdle())
auto flags = wxSizerFlags().Expand(); {
m_sizer->Add(name, flags); for (const auto& newHotkey : buttons.GetButtonList())
m_sizer->Add(keyInput, flags); {
m_controllerTimer->Stop();
m_hotkeys.emplace_back(name, keyInput, hotkey); 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()))
{ {
m_needToSave |= true; case InputButtonType::Keyboard: {
s_hotkeyToFuncMap.erase(cfgHotkey.raw); uKeyboardHotkey newHotkey{};
cfgHotkey = newHotkey; if (cfgHotkey.keyboard.raw != newHotkey.raw)
{
m_needToSave |= true;
s_keyboardHotkeyToFuncMap.erase(cfgHotkey.keyboard.raw);
cfgHotkey.keyboard = newHotkey;
FinalizeInput<uKeyboardHotkey>(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;
} }
FinalizeInput(inputButton); }
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());
inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); if constexpr (std::is_same_v<T, uKeyboardHotkey>)
inputButton->SetLabelText(To_wxString(cfgHotkey)); {
inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this);
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,25 +319,34 @@ 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 ""; if (hotkey.alt)
{
ret_val.append("ALT + ");
}
if (hotkey.ctrl)
{
ret_val.append("CTRL + ");
}
if (hotkey.shift)
{
ret_val.append("SHIFT + ");
}
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);
} }
wxString ret_val;
if (hotkey.alt)
{
ret_val.append("ALT + ");
}
if (hotkey.ctrl)
{
ret_val.append("CTRL + ");
}
if (hotkey.shift)
{
ret_val.append("SHIFT + ");
}
ret_val.append(wxAcceleratorEntry(0, hotkey.key).ToString());
return ret_val; return ret_val;
} }

View file

@ -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);
}; };

View file

@ -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;
} }

View file

@ -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
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////

View file

@ -0,0 +1,70 @@
/* XPM */
const char *X_HOTKEY_SETTINGS[] = {
"64 64 2 1",
" c None",
". c #000000",
" ... ",
" .... ",
" .... ",
" .. .... ",
" .. ...... ",
" ... ...... ",
" ... ...... ",
" ... ....... ",
" .... ........ ... ",
" .... ......... ... ",
" .... ......... ... ",
" .... .......... .... ",
" .... ......... .... ",
" ..... ........... ..... ",
" ..... .......... ..... ",
" ........ ........... ...... ",
" ........ ... ....... ...... ",
" ........... ... ....... ...... ",
" ........... ..... ...... ....... ",
" ........... ..... ...... ........ ",
" ............. ...... ...... ......... ",
" ........ .... ...... .... ..... .... ",
" ........ .... ....... .... ...... .... ",
" ...... .... ...... ..... ....... ..... ",
" ...... ........... ............ .... ",
" ...... .......... .......... .... ",
" ..... .......... .......... .... ",
" ..... .......... ......... ..... ",
" ..... .......... ........ ..... ",
" ..... ........ ........ ...... ",
" ..... ..... ..... ..... ",
" ..... ..... .... ...... ",
" ..... ..... ",
" ... .... ",
" . ............................ .. ",
" ................................ ",
" ................................ ",
" .................................... ",
" .................................... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... .......... ",
" .......... .......... ",
" .......... .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .......... ........ .......... ",
" .................................... ",
" .................................... ",
" .................................... ",
" .................................... ",
" ................................ ",
" ................................ ",
" ............................. "
};

View file

@ -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"

View file

@ -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];

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B