Merge pull request 'feat(hotkeys): Add controller overlay hotkey and fix saving bug' (#14) from fix/hotkey-memory into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/14
This commit is contained in:
Collecting
2025-11-01 21:11:19 +00:00
5 changed files with 92 additions and 62 deletions

View File

@@ -240,24 +240,21 @@ void QtConfig::ReadPathValues() {
void QtConfig::ReadShortcutValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) {
BeginGroup(group);
BeginGroup(name);
UISettings::values.shortcuts.clear();
const int shortcuts_size = BeginArray(std::string("shortcuts"));
for (int i = 0; i < shortcuts_size; ++i) {
SetArrayIndex(i);
const std::string name = ReadStringSetting(std::string("name"));
const std::string group = ReadStringSetting(std::string("group"));
const std::string keyseq = ReadStringSetting(std::string("keyseq"));
const std::string controller_keyseq = ReadStringSetting(std::string("controller_keyseq"));
const int context = ReadIntegerSetting(std::string("context"), Qt::WindowShortcut);
const bool repeat = ReadBooleanSetting(std::string("repeat"), false);
// No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
// for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
// a file dialog in windowed mode
UISettings::values.shortcuts.push_back(
{name,
group,
{ReadStringSetting(std::string("KeySeq"), shortcut.keyseq),
ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq),
shortcut.context,
ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}});
EndGroup(); // name
EndGroup(); // group
{name, group, {keyseq, controller_keyseq, context, repeat}});
}
EndArray();
EndGroup();
}
@@ -445,27 +442,18 @@ void QtConfig::SavePathValues() {
void QtConfig::SaveShortcutValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
// However, their ordering must also be the same.
for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) {
const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut;
BeginGroup(group);
BeginGroup(name);
WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
std::make_optional(default_hotkey.keyseq));
WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
std::make_optional(default_hotkey.controller_keyseq));
WriteIntegerSetting(std::string("Context"), shortcut.context,
std::make_optional(default_hotkey.context));
WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
std::make_optional(default_hotkey.repeat));
EndGroup(); // name
EndGroup(); // group
BeginArray(std::string("shortcuts"));
for (std::size_t i = 0; i < UISettings::values.shortcuts.size(); ++i) {
SetArrayIndex(static_cast<int>(i)); // SetArrayIndex expects an int, so we cast here.
const auto& shortcut = UISettings::values.shortcuts[i];
WriteStringSetting(std::string("name"), shortcut.name);
WriteStringSetting(std::string("group"), shortcut.group);
WriteStringSetting(std::string("keyseq"), shortcut.shortcut.keyseq);
WriteStringSetting(std::string("controller_keyseq"), shortcut.shortcut.controller_keyseq);
WriteIntegerSetting(std::string("context"), shortcut.shortcut.context);
WriteBooleanSetting(std::string("repeat"), shortcut.shortcut.repeat);
}
EndArray();
EndGroup();
}

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream>
@@ -14,45 +15,79 @@ HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
void HotkeyRegistry::SaveHotkeys() {
std::map<std::string, std::map<std::string, Hotkey>> default_groups;
for (const auto& def : UISettings::default_hotkeys) {
default_groups[def.group][def.name] =
Hotkey{QKeySequence::fromString(QString::fromStdString(def.shortcut.keyseq)),
def.shortcut.controller_keyseq,
nullptr,
nullptr,
static_cast<Qt::ShortcutContext>(def.shortcut.context),
def.shortcut.repeat};
}
UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) {
for (const auto& hotkey : group.second) {
UISettings::values.shortcuts.push_back(
{hotkey.first, group.first,
UISettings::ContextualShortcut({hotkey.second.keyseq.toString().toStdString(),
hotkey.second.controller_keyseq,
hotkey.second.context, hotkey.second.repeat})});
for (const auto& [group_name, actions] : hotkey_groups) {
for (const auto& [action_name, current_hotkey] : actions) {
Hotkey default_hotkey;
auto group_it = default_groups.find(group_name);
if (group_it != default_groups.end()) {
auto action_it = group_it->second.find(action_name);
if (action_it != group_it->second.end()) {
default_hotkey = action_it->second;
}
}
bool is_modified =
(current_hotkey.keyseq != default_hotkey.keyseq) ||
(current_hotkey.controller_keyseq != default_hotkey.controller_keyseq) ||
(current_hotkey.context != default_hotkey.context);
if (is_modified) {
UISettings::values.shortcuts.push_back(
{action_name, group_name,
UISettings::ContextualShortcut(
{current_hotkey.keyseq.toString().toStdString(),
current_hotkey.controller_keyseq, current_hotkey.context,
current_hotkey.repeat})});
}
}
}
}
void HotkeyRegistry::LoadHotkeys() {
// Make sure NOT to use a reference here because it would become invalid once we call
// beginGroup()
for (auto shortcut : UISettings::values.shortcuts) {
// First, populate the registry with ALL default hotkeys, including blank ones.
hotkey_groups.clear();
for (const auto& def : UISettings::default_hotkeys) {
Hotkey& hk = hotkey_groups[def.group][def.name];
hk.keyseq = QKeySequence::fromString(QString::fromStdString(def.shortcut.keyseq));
hk.controller_keyseq = def.shortcut.controller_keyseq;
hk.context = static_cast<Qt::ShortcutContext>(def.shortcut.context);
hk.repeat = def.shortcut.repeat;
}
// Now, layer the user's saved (non-default) settings on top.
for (const auto& shortcut : UISettings::values.shortcuts) {
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
if (!shortcut.shortcut.keyseq.empty()) {
hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq),
QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
}
if (!shortcut.shortcut.controller_keyseq.empty()) {
hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
}
hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
hk.repeat = shortcut.shortcut.repeat;
if (hk.shortcut) {
hk.shortcut->disconnect();
hk.shortcut->setKey(hk.keyseq);
}
if (hk.controller_shortcut) {
hk.controller_shortcut->disconnect();
hk.controller_shortcut->SetKey(hk.controller_keyseq);
}
hk.repeat = shortcut.shortcut.repeat;
}
}
QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action,
QWidget* widget) {
QWidget* widget) const {
Hotkey& hk = hotkey_groups[group][action];
if (!hk.shortcut) {
@@ -65,7 +100,7 @@ QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string
ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group,
const std::string& action,
Core::HID::EmulatedController* controller) {
Core::HID::EmulatedController* controller) const {
Hotkey& hk = hotkey_groups[group][action];
if (!hk.controller_shortcut) {
@@ -76,12 +111,12 @@ ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group
return hk.controller_shortcut;
}
QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) {
QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) const {
return hotkey_groups[group][action].keyseq;
}
Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group,
const std::string& action) {
const std::string& action) const {
return hotkey_groups[group][action].context;
}

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -88,9 +89,9 @@ public:
* will be the same. Thus, you shouldn't rely on the caller really being the
* QShortcut's parent.
*/
QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget);
QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget) const;
ControllerShortcut* GetControllerHotkey(const std::string& group, const std::string& action,
Core::HID::EmulatedController* controller);
Core::HID::EmulatedController* controller) const;
/**
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
@@ -98,7 +99,7 @@ public:
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
QKeySequence GetKeySequence(const std::string& group, const std::string& action);
QKeySequence GetKeySequence(const std::string& group, const std::string& action) const;
/**
* Returns a Qt::ShortcutContext object who can be connected to other
@@ -108,7 +109,7 @@ public:
* "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action);
Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action) const;
private:
struct Hotkey {
@@ -123,5 +124,5 @@ private:
using HotkeyMap = std::map<std::string, Hotkey>;
using HotkeyGroupMap = std::map<std::string, HotkeyMap>;
HotkeyGroupMap hotkey_groups;
mutable HotkeyGroupMap hotkey_groups;
};

View File

@@ -357,6 +357,9 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
ui->setupUi(this);
statusBar()->hide();
// Controller Overlay toggle
ui->actionControllerOverlay->setCheckable(true);
// Check dark mode before a theme is loaded
os_dark_mode = CheckDarkMode();
startup_icon_theme = QIcon::themeName();
@@ -1388,6 +1391,7 @@ void GMainWindow::InitializeHotkeys() {
LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
LinkActionShortcut(ui->action_Show_Vram_Overlay, QStringLiteral("Toggle VRAM Overlay"));
LinkActionShortcut(ui->actionControllerOverlay, QStringLiteral("Toggle Controller Overlay"));
LinkActionShortcut(ui->action_Show_Multiplayer_Room_Overlay, QStringLiteral("Toggle Multiplayer Room Overlay"));
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
@@ -6069,6 +6073,7 @@ int main(int argc, char* argv[]) {
#endif
#ifdef CITRON_USE_AUTO_UPDATER
// Check for and apply staged updates before starting the main application
std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
#ifdef _WIN32

View File

@@ -239,7 +239,7 @@ namespace UISettings {
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<Shortcut, 29> default_hotkeys{{
const std::array<Shortcut, 30> default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
@@ -263,6 +263,7 @@ namespace UISettings {
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Controller Overlay")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Grid View")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+G"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}},