diff options
Diffstat (limited to '')
45 files changed, 751 insertions, 650 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d4460bf01..3e6106f0a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -3,7 +3,14 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) set(SRCS - config.cpp + configuration/config.cpp + configuration/configure_audio.cpp + configuration/configure_debug.cpp + configuration/configure_dialog.cpp + configuration/configure_general.cpp + configuration/configure_graphics.cpp + configuration/configure_input.cpp + configuration/configure_system.cpp debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics/graphics.cpp @@ -19,13 +26,6 @@ set(SRCS util/spinbox.cpp util/util.cpp bootmanager.cpp - configure_audio.cpp - configure_debug.cpp - configure_dialog.cpp - configure_general.cpp - configure_graphics.cpp - configure_system.cpp - configure_input.cpp game_list.cpp hotkeys.cpp main.cpp @@ -35,7 +35,14 @@ set(SRCS ) set(HEADERS - config.h + configuration/config.h + configuration/configure_audio.h + configuration/configure_debug.h + configuration/configure_dialog.h + configuration/configure_general.h + configuration/configure_graphics.h + configuration/configure_input.h + configuration/configure_system.h debugger/callstack.h debugger/disassembler.h debugger/graphics/graphics.h @@ -52,13 +59,6 @@ set(HEADERS util/spinbox.h util/util.h bootmanager.h - configure_audio.h - configure_debug.h - configure_dialog.h - configure_general.h - configure_graphics.h - configure_system.h - configure_input.h game_list.h game_list_p.h hotkeys.h @@ -67,17 +67,16 @@ set(HEADERS ) set(UIS + configuration/configure.ui + configuration/configure_audio.ui + configuration/configure_debug.ui + configuration/configure_general.ui + configuration/configure_graphics.ui + configuration/configure_input.ui + configuration/configure_system.ui debugger/callstack.ui debugger/disassembler.ui - debugger/profiler.ui debugger/registers.ui - configure.ui - configure_audio.ui - configure_debug.ui - configure_general.ui - configure_graphics.ui - configure_system.ui - configure_input.ui hotkeys.ui main.ui ) @@ -98,7 +97,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt core video_core audio_core common) +target_link_libraries(citra-qt core video_core audio_core common input_common) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 948db384d..bae576d6a 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -13,7 +13,9 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "core/core.h" -#include "core/frontend/key_map.h" +#include "core/settings.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/video_core.h" @@ -99,14 +101,17 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { + : QWidget(parent), child(nullptr), emu_thread(emu_thread) { - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); - keyboard_id = KeyMap::NewDeviceId(); - ReloadSetKeymaps(); + InputCommon::Init(); +} + +GRenderWindow::~GRenderWindow() { + InputCommon::Shutdown(); } void GRenderWindow::moveContext() { @@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { } void GRenderWindow::keyPressEvent(QKeyEvent* event) { - KeyMap::PressKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->PressKey(event->key()); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - KeyMap::ReleaseKey(*this, {event->key(), keyboard_id}); + InputCommon::GetKeyboard()->ReleaseKey(event->key()); } void GRenderWindow::mousePressEvent(QMouseEvent* event) { @@ -230,13 +235,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { motion_emu->EndTilt(); } -void GRenderWindow::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping( - {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, - KeyMap::mapping_targets[i]); - } +void GRenderWindow::focusOutEvent(QFocusEvent* event) { + QWidget::focusOutEvent(event); + InputCommon::GetKeyboard()->ReleaseAllKeys(); } void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 7dac1c480..9d39f1af8 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow { public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); + ~GRenderWindow(); // EmuWindow implementation void SwapBuffers() override; @@ -127,7 +128,7 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; - void ReloadSetKeymaps() override; + void focusOutEvent(QFocusEvent* event) override; void OnClientAreaResized(unsigned width, unsigned height); @@ -152,9 +153,6 @@ private: QByteArray geometry; - /// Device id of keyboard for use with KeyMap - int keyboard_id; - EmuThread* emu_thread; /// Motion sensors emulation diff --git a/src/citra_qt/config.cpp b/src/citra_qt/configuration/config.cpp index b65f57fdc..0b9b73f9e 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -3,9 +3,10 @@ // Refer to the license.txt file included. #include <QSettings> -#include "citra_qt/config.h" +#include "citra_qt/configuration/config.h" #include "citra_qt/ui_settings.h" #include "common/file_util.h" +#include "input_common/main.h" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -16,25 +17,46 @@ Config::Config() { Reload(); } -const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = { - // directly mapped keys - Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, - Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I, - Qt::Key_K, Qt::Key_J, Qt::Key_L, - - // indirectly mapped keys - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, +const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { + Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, + Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B, }; +const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ + { + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, + }, + { + Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D, + }, +}}; + void Config::ReadValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]) - .toInt(); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.buttons[i] = + qt_config + ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.buttons[i].empty()) + Settings::values.buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.analogs[i] = + qt_config + ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.analogs[i].empty()) + Settings::values.analogs[i] = default_param; } - Settings::values.pad_circle_modifier_scale = - qt_config->value("pad_circle_modifier_scale", 0.5).toFloat(); + qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -57,6 +79,15 @@ void Config::ReadValues() { Settings::values.layout_option = static_cast<Settings::LayoutOption>(qt_config->value("layout_option").toInt()); Settings::values.swap_screen = qt_config->value("swap_screen", false).toBool(); + Settings::values.custom_layout = qt_config->value("custom_layout", false).toBool(); + Settings::values.custom_top_left = qt_config->value("custom_top_left", 0).toInt(); + Settings::values.custom_top_top = qt_config->value("custom_top_top", 0).toInt(); + Settings::values.custom_top_right = qt_config->value("custom_top_right", 400).toInt(); + Settings::values.custom_top_bottom = qt_config->value("custom_top_bottom", 240).toInt(); + Settings::values.custom_bottom_left = qt_config->value("custom_bottom_left", 40).toInt(); + Settings::values.custom_bottom_top = qt_config->value("custom_bottom_top", 240).toInt(); + Settings::values.custom_bottom_right = qt_config->value("custom_bottom_right", 360).toInt(); + Settings::values.custom_bottom_bottom = qt_config->value("custom_bottom_bottom", 480).toInt(); qt_config->endGroup(); qt_config->beginGroup("Audio"); @@ -146,6 +177,7 @@ void Config::ReadValues() { UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -154,12 +186,14 @@ void Config::ReadValues() { void Config::SaveValues() { qt_config->beginGroup("Controls"); - for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), - Settings::values.input_mappings[Settings::NativeInput::All[i]]); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.buttons[i])); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.analogs[i])); } - qt_config->setValue("pad_circle_modifier_scale", - (double)Settings::values.pad_circle_modifier_scale); qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -182,6 +216,15 @@ void Config::SaveValues() { qt_config->beginGroup("Layout"); qt_config->setValue("layout_option", static_cast<int>(Settings::values.layout_option)); qt_config->setValue("swap_screen", Settings::values.swap_screen); + qt_config->setValue("custom_layout", Settings::values.custom_layout); + qt_config->setValue("custom_top_left", Settings::values.custom_top_left); + qt_config->setValue("custom_top_top", Settings::values.custom_top_top); + qt_config->setValue("custom_top_right", Settings::values.custom_top_right); + qt_config->setValue("custom_top_bottom", Settings::values.custom_top_bottom); + qt_config->setValue("custom_bottom_left", Settings::values.custom_bottom_left); + qt_config->setValue("custom_bottom_top", Settings::values.custom_bottom_top); + qt_config->setValue("custom_bottom_right", Settings::values.custom_bottom_right); + qt_config->setValue("custom_bottom_bottom", Settings::values.custom_bottom_bottom); qt_config->endGroup(); qt_config->beginGroup("Audio"); @@ -252,6 +295,7 @@ void Config::SaveValues() { qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); qt_config->setValue("firstStart", UISettings::values.first_start); diff --git a/src/citra_qt/config.h b/src/citra_qt/configuration/config.h index 79c901804..cbf745ea2 100644 --- a/src/citra_qt/config.h +++ b/src/citra_qt/configuration/config.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <string> #include <QVariant> #include "core/settings.h" @@ -23,5 +24,7 @@ public: void Reload(); void Save(); - static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults; + + static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; + static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; }; diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configuration/configure.ui index 28b4a3b90..85e206e42 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -64,37 +64,37 @@ <customwidget> <class>ConfigureGeneral</class> <extends>QWidget</extends> - <header>configure_general.h</header> + <header>configuration/configure_general.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> - <header>configure_system.h</header> + <header>configuration/configure_system.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> - <header>configure_audio.h</header> + <header>configuration/configure_audio.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> - <header>configure_debug.h</header> + <header>configuration/configure_debug.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureInput</class> <extends>QWidget</extends> - <header>configure_input.h</header> + <header>configuration/configure_input.h</header> <container>1</container> </customwidget> <customwidget> <class>ConfigureGraphics</class> <extends>QWidget</extends> - <header>configure_graphics.h</header> + <header>configuration/configure_graphics.h</header> <container>1</container> </customwidget> </customwidgets> diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 3ddcf9232..3fd1d127a 100644 --- a/src/citra_qt/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -6,7 +6,7 @@ #include "audio_core/audio_core.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" -#include "citra_qt/configure_audio.h" +#include "citra_qt/configuration/configure_audio.h" #include "core/settings.h" #include "ui_configure_audio.h" diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index 8190e694f..8190e694f 100644 --- a/src/citra_qt/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index dd870eb61..dd870eb61 100644 --- a/src/citra_qt/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui diff --git a/src/citra_qt/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index dcc398eee..263f73f38 100644 --- a/src/citra_qt/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_debug.h" +#include "citra_qt/configuration/configure_debug.h" #include "core/settings.h" #include "ui_configure_debug.h" diff --git a/src/citra_qt/configure_debug.h b/src/citra_qt/configuration/configure_debug.h index d167eb996..d167eb996 100644 --- a/src/citra_qt/configure_debug.h +++ b/src/citra_qt/configuration/configure_debug.h diff --git a/src/citra_qt/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index bbbb0e3f4..bbbb0e3f4 100644 --- a/src/citra_qt/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 525a7cc4e..dfc8c03a7 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "core/settings.h" #include "ui_configure.h" diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index 21fa1f501..21fa1f501 100644 --- a/src/citra_qt/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index ac90a6df4..a21176c34 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_general.h" +#include "citra_qt/configuration/configure_general.h" #include "citra_qt/ui_settings.h" #include "core/core.h" #include "core/settings.h" diff --git a/src/citra_qt/configure_general.h b/src/citra_qt/configuration/configure_general.h index 447552d8c..447552d8c 100644 --- a/src/citra_qt/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 0f3352a1d..c739605a4 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -27,7 +27,7 @@ <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> - <string>Recursive scan for game folder</string> + <string>Search sub-directories for games</string> </property> </widget> </item> diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 54f799b47..b5a5ab1e1 100644 --- a/src/citra_qt/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_graphics.h" +#include "citra_qt/configuration/configure_graphics.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" @@ -14,6 +14,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) this->setConfiguration(); ui->toggle_vsync->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + + ui->layoutBox->setEnabled(!Settings::values.custom_layout); } ConfigureGraphics::~ConfigureGraphics() {} diff --git a/src/citra_qt/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 5497a55f7..5497a55f7 100644 --- a/src/citra_qt/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index a091f4c60..228f2a869 100644 --- a/src/citra_qt/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -126,7 +126,7 @@ </layout> </item> <item> - <widget class="QGroupBox" name="groupBox2"> + <widget class="QGroupBox" name="layoutBox"> <property name="title"> <string>Layout</string> </property> diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp new file mode 100644 index 000000000..daac9b63a --- /dev/null +++ b/src/citra_qt/configuration/configure_input.cpp @@ -0,0 +1,205 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QTimer> +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_input.h" +#include "common/param_package.h" +#include "input_common/main.h" + +const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> + ConfigureInput::analog_sub_buttons{{ + "up", "down", "left", "right", "modifier", + }}; + +static QString getKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static void SetButtonKey(int key, Common::ParamPackage& button_param) { + button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)}; +} + +static void SetAnalogKey(int key, Common::ParamPackage& analog_param, + const std::string& button_name) { + if (analog_param.Get("engine", "") != "analog_from_button") { + analog_param = { + {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, + }; + } + analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key)); +} + +ConfigureInput::ConfigureInput(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), + timer(std::make_unique<QTimer>()) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp, + ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR, + ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome, + }; + + analog_map = {{ + { + ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight, + ui->buttonCircleMod, + }, + { + ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight, + nullptr, + }, + }}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id]) + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick(button_map[button_id], + [=](int key) { SetButtonKey(key, buttons_param[button_id]); }); + }); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map[analog_id][sub_button_id] != nullptr) { + connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map[analog_id][sub_button_id], [=](int key) { + SetAnalogKey(key, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }); + }); + } + } + } + + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timer->setSingleShot(true); + connect(timer.get(), &QTimer::timeout, [this]() { + releaseKeyboard(); + releaseMouse(); + key_setter = boost::none; + updateButtonLabels(); + }); + + this->loadConfiguration(); + + // TODO(wwylele): enable these when the input emulation for them is implemented + ui->buttonZL->setEnabled(false); + ui->buttonZR->setEnabled(false); + ui->buttonHome->setEnabled(false); + ui->buttonCStickUp->setEnabled(false); + ui->buttonCStickDown->setEnabled(false); + ui->buttonCStickLeft->setEnabled(false); + ui->buttonCStickRight->setEnabled(false); +} + +void ConfigureInput::applyConfiguration() { + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + Settings::Apply(); +} + +void ConfigureInput::loadConfiguration() { + std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), + buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), + analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + updateButtonLabels(); +} + +void ConfigureInput::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + SetAnalogKey(Config::default_analogs[analog_id][sub_button_id], + analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); + applyConfiguration(); +} + +void ConfigureInput::updateButtonLabels() { + QString non_keyboard(tr("[non-keyboard]")); + + auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) { + if (param.Get("engine", "") != "keyboard") { + return non_keyboard; + } else { + return getKeyName(param.Get("code", 0)); + } + }; + + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + button_map[button]->setText(KeyToText(buttons_param[button])); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") { + for (QPushButton* button : analog_map[analog_id]) { + if (button) + button->setText(non_keyboard); + } + } else { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + Common::ParamPackage param( + analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], "")); + if (analog_map[analog_id][sub_button_id]) + analog_map[analog_id][sub_button_id]->setText(KeyToText(param)); + } + } + } +} + +void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) { + button->setText(tr("[press key]")); + button->setFocus(); + + key_setter = new_key_setter; + + grabKeyboard(); + grabMouse(); + timer->start(5000); // Cancel after 5 seconds +} + +void ConfigureInput::keyPressEvent(QKeyEvent* event) { + releaseKeyboard(); + releaseMouse(); + + if (!key_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) + (*key_setter)(event->key()); + + updateButtonLabels(); + key_setter = boost::none; + timer->stop(); +} diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h new file mode 100644 index 000000000..c950fbcb4 --- /dev/null +++ b/src/citra_qt/configuration/configure_input.h @@ -0,0 +1,69 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <string> +#include <QKeyEvent> +#include <QWidget> +#include <boost/optional.hpp> +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInput; +} + +class ConfigureInput : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInput(QWidget* parent = nullptr); + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureInput> ui; + + std::unique_ptr<QTimer> timer; + + /// This will be the the setting function when an input is awaiting configuration. + boost::optional<std::function<void(int)>> key_setter; + + std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + + /// Each button input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + + /// Each analog input is represented by five QPushButtons which represents up, down, left, right + /// and modifier + std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> + analog_map; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, std::function<void(int)> new_key_setter); + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; +}; diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2760787e5..2760787e5 100644 --- a/src/citra_qt/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index eb1276ef3..a3a9015a4 100644 --- a/src/citra_qt/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -2,8 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "citra_qt/configure_system.h" +#include "citra_qt/configuration/configure_system.h" #include "citra_qt/ui_settings.h" +#include "core/core.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "ui_configure_system.h" diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configuration/configure_system.h index db0ead13c..db0ead13c 100644 --- a/src/citra_qt/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index cc54fa37f..cc54fa37f 100644 --- a/src/citra_qt/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp deleted file mode 100644 index 3e6803b8a..000000000 --- a/src/citra_qt/configure_input.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> -#include <utility> -#include <QTimer> -#include "citra_qt/config.h" -#include "citra_qt/configure_input.h" - -static QString getKeyName(Qt::Key key_code) { - switch (key_code) { - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - case -1: - return ""; - default: - return QKeySequence(key_code).toString(); - } -} - -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - timer(std::make_unique<QTimer>()) { - - ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - {Settings::NativeInput::Values::A, ui->buttonA}, - {Settings::NativeInput::Values::B, ui->buttonB}, - {Settings::NativeInput::Values::X, ui->buttonX}, - {Settings::NativeInput::Values::Y, ui->buttonY}, - {Settings::NativeInput::Values::L, ui->buttonL}, - {Settings::NativeInput::Values::R, ui->buttonR}, - {Settings::NativeInput::Values::ZL, ui->buttonZL}, - {Settings::NativeInput::Values::ZR, ui->buttonZR}, - {Settings::NativeInput::Values::START, ui->buttonStart}, - {Settings::NativeInput::Values::SELECT, ui->buttonSelect}, - {Settings::NativeInput::Values::HOME, ui->buttonHome}, - {Settings::NativeInput::Values::DUP, ui->buttonDpadUp}, - {Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown}, - {Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft}, - {Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight}, - {Settings::NativeInput::Values::CUP, ui->buttonCStickUp}, - {Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown}, - {Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft}, - {Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight}, - {Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp}, - {Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown}, - {Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft}, - {Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight}, - {Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod}, - }; - - for (const auto& entry : button_map) { - const Settings::NativeInput::Values input_id = entry.first; - connect(entry.second, &QPushButton::released, - [this, input_id]() { handleClick(input_id); }); - } - - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); - - timer->setSingleShot(true); - connect(timer.get(), &QTimer::timeout, [this]() { - releaseKeyboard(); - releaseMouse(); - current_input_id = boost::none; - updateButtonLabels(); - }); - - this->loadConfiguration(); -} - -void ConfigureInput::applyConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]); - } - Settings::Apply(); -} - -void ConfigureInput::loadConfiguration() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]); - } - updateButtonLabels(); -} - -void ConfigureInput::restoreDefaults() { - for (const auto& input_id : Settings::NativeInput::All) { - const size_t index = static_cast<size_t>(input_id); - key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt()); - } - updateButtonLabels(); - applyConfiguration(); -} - -void ConfigureInput::updateButtonLabels() { - for (const auto& input_id : Settings::NativeInput::All) { - button_map[input_id]->setText(getKeyName(key_map[input_id])); - } -} - -void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) { - QPushButton* button = button_map[input_id]; - button->setText(tr("[press key]")); - button->setFocus(); - - current_input_id = input_id; - - grabKeyboard(); - grabMouse(); - timer->start(5000); // Cancel after 5 seconds -} - -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - releaseKeyboard(); - releaseMouse(); - - if (!current_input_id || !event) - return; - - if (event->key() != Qt::Key_Escape) - setInput(*current_input_id, static_cast<Qt::Key>(event->key())); - - updateButtonLabels(); - current_input_id = boost::none; - timer->stop(); -} - -void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) { - // Remove duplicates - for (auto& pair : key_map) { - if (pair.second == key_pressed) - pair.second = Qt::Key_unknown; - } - - key_map[input_id] = key_pressed; -} diff --git a/src/citra_qt/configure_input.h b/src/citra_qt/configure_input.h deleted file mode 100644 index bc343db83..000000000 --- a/src/citra_qt/configure_input.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <QKeyEvent> -#include <QWidget> -#include <boost/optional.hpp> -#include "core/settings.h" -#include "ui_configure_input.h" - -class QPushButton; -class QString; -class QTimer; - -namespace Ui { -class ConfigureInput; -} - -class ConfigureInput : public QWidget { - Q_OBJECT - -public: - explicit ConfigureInput(QWidget* parent = nullptr); - - /// Save all button configurations to settings file - void applyConfiguration(); - -private: - std::unique_ptr<Ui::ConfigureInput> ui; - - /// This input is currently awaiting configuration. - /// (i.e.: its corresponding QPushButton has been pressed.) - boost::optional<Settings::NativeInput::Values> current_input_id; - std::unique_ptr<QTimer> timer; - - /// Each input is represented by a QPushButton. - std::map<Settings::NativeInput::Values, QPushButton*> button_map; - /// Each input is configured to respond to the press of a Qt::Key. - std::map<Settings::NativeInput::Values, Qt::Key> key_map; - - /// Load configuration settings. - void loadConfiguration(); - /// Restore all buttons to their default values. - void restoreDefaults(); - /// Update UI to reflect current configuration. - void updateButtonLabels(); - - /// Called when the button corresponding to input_id was pressed. - void handleClick(Settings::NativeInput::Values input_id); - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; - /// Configure input input_id to respond to key key_pressed. - void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed); -}; diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index f5a2ec761..c68fe753b 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -18,15 +18,16 @@ #include "citra_qt/util/util.h" #include "common/vector_math.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs.h" +#include "video_core/texture/texture_decode.h" namespace { -QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { +QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) { QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); for (int y = 0; y < info.height; ++y) { for (int x = 0; x < info.width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } @@ -36,9 +37,10 @@ QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) { class TextureInfoWidget : public QWidget { public: - TextureInfoWidget(const u8* src, const Pica::DebugUtils::TextureInfo& info, + TextureInfoWidget(const u8* src, const Pica::Texture::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { + QLabel* image_widget = new QLabel; QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info)); image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -70,7 +72,7 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case 0: - return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str()); + return QString::fromLatin1(Pica::Regs::GetRegisterName(write.cmd_id)); case 1: return QString("%1").arg(write.cmd_id, 3, 16, QLatin1Char('0')); case 2: @@ -121,15 +123,16 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; - } else if (COMMAND_IN_RANGE(command_id, texture2)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture2)) { texture_index = 2; } else { UNREACHABLE_MSG("Unknown texture command"); @@ -144,23 +147,24 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { const unsigned int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt(); - if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) || - COMMAND_IN_RANGE(command_id, texture2)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0) || + COMMAND_IN_RANGE(command_id, texturing.texture1) || + COMMAND_IN_RANGE(command_id, texturing.texture2)) { unsigned texture_index; - if (COMMAND_IN_RANGE(command_id, texture0)) { + if (COMMAND_IN_RANGE(command_id, texturing.texture0)) { texture_index = 0; - } else if (COMMAND_IN_RANGE(command_id, texture1)) { + } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) { texture_index = 1; } else { texture_index = 2; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index]; const auto config = texture.config; const auto format = texture.format; - const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); + const auto info = Pica::Texture::TextureInfo::FromPicaRegister(config, format); const u8* src = Memory::GetPhysicalPointer(config.GetPhysicalAddress()); new_info_widget = new TextureInfoWidget(src, info); } diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp index 4efd95d3c..47d9924e1 100644 --- a/src/citra_qt/debugger/graphics/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp @@ -16,8 +16,10 @@ #include "common/color.h" #include "core/hw/gpu.h" #include "core/memory.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/regs_framebuffer.h" +#include "video_core/regs_texturing.h" +#include "video_core/texture/texture_decode.h" #include "video_core/utils.h" SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) @@ -413,30 +415,30 @@ void GraphicsSurfaceWidget::OnUpdate() { // TODO: Store a reference to the registers in the debug context instead of accessing them // directly... - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetColorBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.color_format) { - case Pica::Regs::ColorFormat::RGBA8: + case Pica::FramebufferRegs::ColorFormat::RGBA8: surface_format = Format::RGBA8; break; - case Pica::Regs::ColorFormat::RGB8: + case Pica::FramebufferRegs::ColorFormat::RGB8: surface_format = Format::RGB8; break; - case Pica::Regs::ColorFormat::RGB5A1: + case Pica::FramebufferRegs::ColorFormat::RGB5A1: surface_format = Format::RGB5A1; break; - case Pica::Regs::ColorFormat::RGB565: + case Pica::FramebufferRegs::ColorFormat::RGB565: surface_format = Format::RGB565; break; - case Pica::Regs::ColorFormat::RGBA4: + case Pica::FramebufferRegs::ColorFormat::RGBA4: surface_format = Format::RGBA4; break; @@ -449,22 +451,22 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::DepthBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D16: + case Pica::FramebufferRegs::DepthFormat::D16: surface_format = Format::D16; break; - case Pica::Regs::DepthFormat::D24: + case Pica::FramebufferRegs::DepthFormat::D24: surface_format = Format::D24; break; - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::D24X8; break; @@ -477,14 +479,14 @@ void GraphicsSurfaceWidget::OnUpdate() { } case Source::StencilBuffer: { - const auto& framebuffer = Pica::g_state.regs.framebuffer; + const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer; surface_address = framebuffer.GetDepthBufferPhysicalAddress(); surface_width = framebuffer.GetWidth(); surface_height = framebuffer.GetHeight(); switch (framebuffer.depth_format) { - case Pica::Regs::DepthFormat::D24S8: + case Pica::FramebufferRegs::DepthFormat::D24S8: surface_format = Format::X24S8; break; @@ -511,8 +513,8 @@ void GraphicsSurfaceWidget::OnUpdate() { break; } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); + const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index]; + auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); surface_address = info.physical_address; surface_width = info.width; @@ -567,28 +569,27 @@ void GraphicsSurfaceWidget::OnUpdate() { surface_picture_label->show(); - unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); - unsigned stride = nibbles_per_pixel * surface_width / 2; - - // We handle depth formats here because DebugUtils only supports TextureFormats if (surface_format <= Format::MaxTextureFormat) { - // Generate a virtual texture - Pica::DebugUtils::TextureInfo info; + Pica::Texture::TextureInfo info; info.physical_address = surface_address; info.width = surface_width; info.height = surface_height; - info.format = static_cast<Pica::Regs::TextureFormat>(surface_format); - info.stride = stride; + info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface_format); + info.SetDefaultStride(); for (unsigned int y = 0; y < surface_height; ++y) { for (unsigned int x = 0; x < surface_width; ++x) { - Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true); + Math::Vec4<u8> color = Pica::Texture::LookupTexture(buffer, x, y, info, true); decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); } } - } else { + // We handle depth formats here because DebugUtils only supports TextureFormats + + // TODO(yuriks): Convert to newer tile-based addressing + unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); + unsigned stride = nibbles_per_pixel * surface_width / 2; ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel"); @@ -689,7 +690,8 @@ void GraphicsSurfaceWidget::SaveSurface() { unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) { if (format <= Format::MaxTextureFormat) { - return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format)); + return Pica::TexturingRegs::NibblesPerPixel( + static_cast<Pica::TexturingRegs::TextureFormat>(format)); } switch (format) { diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp index 716ed50b8..40d5bed51 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp +++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp @@ -18,7 +18,6 @@ #include "core/hw/lcd.h" #include "core/tracer/recorder.h" #include "nihstro/float24.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, @@ -71,8 +70,8 @@ void GraphicsTracingWidget::StartRecording() { std::array<u32, 4 * 16> default_attributes; for (unsigned i = 0; i < 16; ++i) { for (unsigned comp = 0; comp < 3; ++comp) { - default_attributes[4 * i + comp] = - nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32()); + default_attributes[4 * i + comp] = nihstro::to_float24( + Pica::g_state.input_default_attributes.attr[i][comp].ToFloat32()); } } diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.h b/src/citra_qt/debugger/graphics/graphics_tracing.h index 3f73bcd2e..eb1292c29 100644 --- a/src/citra_qt/debugger/graphics/graphics_tracing.h +++ b/src/citra_qt/debugger/graphics/graphics_tracing.h @@ -15,6 +15,9 @@ public: explicit GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + void OnEmulationStarting(EmuThread* emu_thread); + void OnEmulationStopping(); + private slots: void StartRecording(); void StopRecording(); @@ -23,9 +26,6 @@ private slots: void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; void OnResumed() override; - void OnEmulationStarting(EmuThread* emu_thread); - void OnEmulationStopping(); - signals: void SetStartTracingButtonEnabled(bool enable); void SetStopTracingButtonEnabled(bool enable); diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index f37524190..e3f3194db 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -16,7 +16,6 @@ #include <QTreeView> #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" #include "citra_qt/util/util.h" -#include "video_core/pica.h" #include "video_core/pica_state.h" #include "video_core/shader/debug_data.h" #include "video_core/shader/shader.h" @@ -359,7 +358,7 @@ void GraphicsVertexShaderWidget::DumpShader() { auto& config = Pica::g_state.regs.vs; Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup, - Pica::g_state.regs.vs_output_attributes); + Pica::g_state.regs.rasterizer.vs_output_attributes); } GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( @@ -511,7 +510,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d auto& shader_config = Pica::g_state.regs.vs; for (auto instr : shader_setup.program_code) info.code.push_back({instr}); - int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); + int num_attributes = shader_config.max_input_attribute_index + 1; for (auto pattern : shader_setup.swizzle_data) info.swizzle_info.push_back({pattern}); @@ -522,11 +521,11 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d // Generate debug information Pica::Shader::InterpreterEngine shader_engine; shader_engine.SetupBatch(shader_setup, entry_point); - debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, num_attributes); + debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, shader_config); // Reload widget state for (int attr = 0; attr < num_attributes; ++attr) { - unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); + unsigned source_attr = shader_config.GetRegisterForAttribute(attr); input_data_mapping[attr]->setText(QString("-> v%1").arg(source_attr)); input_data_container[attr]->setVisible(true); } diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h index 3292573f3..c249a2ff8 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h @@ -82,7 +82,7 @@ private: nihstro::ShaderInfo info; Pica::Shader::DebugData<true> debug_data; - Pica::Shader::InputVertex input_vertex; + Pica::Shader::AttributeBuffer input_vertex; friend class GraphicsVertexShaderModel; }; diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index cee10403d..f060bbe08 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QAction> +#include <QLayout> #include <QMouseEvent> #include <QPainter> #include <QString> @@ -9,121 +11,12 @@ #include "citra_qt/util/util.h" #include "common/common_types.h" #include "common/microprofile.h" -#include "common/profiler_reporting.h" // Include the implementation of the UI in this file. This isn't in microprofile.cpp because the // non-Qt frontends don't need it (and don't implement the UI drawing hooks either). #if MICROPROFILE_ENABLED #define MICROPROFILEUI_IMPL 1 #include "common/microprofileui.h" -#endif - -using namespace Common::Profiling; - -static QVariant GetDataForColumn(int col, const AggregatedDuration& duration) { - static auto duration_to_float = [](Duration dur) -> float { - using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>; - return std::chrono::duration_cast<FloatMs>(dur).count(); - }; - - switch (col) { - case 1: - return duration_to_float(duration.avg); - case 2: - return duration_to_float(duration.min); - case 3: - return duration_to_float(duration.max); - default: - return QVariant(); - } -} - -ProfilerModel::ProfilerModel(QObject* parent) : QAbstractItemModel(parent) { - updateProfilingInfo(); -} - -QVariant ProfilerModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: - return tr("Category"); - case 1: - return tr("Avg"); - case 2: - return tr("Min"); - case 3: - return tr("Max"); - } - } - - return QVariant(); -} - -QModelIndex ProfilerModel::index(int row, int column, const QModelIndex& parent) const { - return createIndex(row, column); -} - -QModelIndex ProfilerModel::parent(const QModelIndex& child) const { - return QModelIndex(); -} - -int ProfilerModel::columnCount(const QModelIndex& parent) const { - return 4; -} - -int ProfilerModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } else { - return 2; - } -} - -QVariant ProfilerModel::data(const QModelIndex& index, int role) const { - if (role == Qt::DisplayRole) { - if (index.row() == 0) { - if (index.column() == 0) { - return tr("Frame"); - } else { - return GetDataForColumn(index.column(), results.frame_time); - } - } else if (index.row() == 1) { - if (index.column() == 0) { - return tr("Frame (with swapping)"); - } else { - return GetDataForColumn(index.column(), results.interframe_time); - } - } - } - - return QVariant(); -} - -void ProfilerModel::updateProfilingInfo() { - results = GetTimingResultsAggregator()->GetAggregatedResults(); - emit dataChanged(createIndex(0, 1), createIndex(rowCount() - 1, 3)); -} - -ProfilerWidget::ProfilerWidget(QWidget* parent) : QDockWidget(parent) { - ui.setupUi(this); - - model = new ProfilerModel(this); - ui.treeView->setModel(model); - - connect(this, SIGNAL(visibilityChanged(bool)), SLOT(setProfilingInfoUpdateEnabled(bool))); - connect(&update_timer, SIGNAL(timeout()), model, SLOT(updateProfilingInfo())); -} - -void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable) { - if (enable) { - update_timer.start(100); - model->updateProfilingInfo(); - } else { - update_timer.stop(); - } -} - -#if MICROPROFILE_ENABLED class MicroProfileWidget : public QWidget { public: diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index c8912fd5a..eae1e9e3c 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -8,46 +8,6 @@ #include <QDockWidget> #include <QTimer> #include "common/microprofile.h" -#include "common/profiler_reporting.h" -#include "ui_profiler.h" - -class ProfilerModel : public QAbstractItemModel { - Q_OBJECT - -public: - explicit ProfilerModel(QObject* parent); - - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, - const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& child) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - -public slots: - void updateProfilingInfo(); - -private: - Common::Profiling::AggregatedFrameResult results; -}; - -class ProfilerWidget : public QDockWidget { - Q_OBJECT - -public: - explicit ProfilerWidget(QWidget* parent = nullptr); - -private slots: - void setProfilingInfoUpdateEnabled(bool enable); - -private: - Ui::Profiler ui; - ProfilerModel* model; - - QTimer update_timer; -}; class MicroProfileDialog : public QWidget { Q_OBJECT diff --git a/src/citra_qt/debugger/profiler.ui b/src/citra_qt/debugger/profiler.ui deleted file mode 100644 index d3c9a9a1f..000000000 --- a/src/citra_qt/debugger/profiler.ui +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Profiler</class> - <widget class="QDockWidget" name="Profiler"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Profiler</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeView" name="treeView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 09469f3c5..a9ec9e830 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QFileInfo> #include <QHeaderView> #include <QMenu> #include <QThreadPool> @@ -38,11 +39,13 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(tree_view); setLayout(layout); } @@ -102,6 +105,12 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { item_model->removeRows(0, item_model->rowCount()); emit ShouldCancelWorker(); + + auto watch_dirs = watcher.directories(); + if (!watch_dirs.isEmpty()) { + watcher.removePaths(watch_dirs); + } + UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -131,6 +140,53 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } +const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", + "cci", "cxi", "app"}; + +static bool HasSupportedFileExtension(const std::string& file_name) { + QFileInfo file = QFileInfo(file_name.c_str()); + return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); +} + +void GameList::RefreshGameDirectory() { + if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + +/** + * Adds the game list folder to the QFileSystemWatcher to check for updates. + * + * The file watcher will fire off an update to the game list when a change is detected in the game + * list folder. + * + * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and + * this function is fast enough to not stall the UI thread. If performance is an issue, it should + * be moved to another thread and properly locked to prevent concurrency issues. + * + * @param dir folder to check for changes in + * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each + * directory recursively to the watcher and will update the file list if any of the folders + * change. The number determines how deep the recursion should traverse. + */ +void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { + const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (FileUtil::IsDirectory(physical_name)) { + UpdateWatcherList(physical_name, recursion - 1); + } + return true; + }; + + watcher.addPath(QString::fromStdString(dir)); + if (recursion > 0) { + FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); + } +} + void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -139,7 +195,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (stop_processing) return false; // Breaks the callback loop. - if (!FileUtil::IsDirectory(physical_name)) { + if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -173,6 +229,6 @@ void GameListWorker::run() { } void GameListWorker::Cancel() { - disconnect(this, nullptr, nullptr, nullptr); + this->disconnect(); stop_processing = true; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 1abf10051..b141fa3a5 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -4,6 +4,7 @@ #pragma once +#include <QFileSystemWatcher> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -33,6 +34,8 @@ public: void SaveInterfaceLayout(); void LoadInterfaceLayout(); + static const QStringList supported_file_extensions; + signals: void GameChosen(QString game_path); void ShouldCancelWorker(); @@ -44,8 +47,11 @@ private: void DonePopulating(); void PopupContextMenu(const QPoint& menu_location); + void UpdateWatcherList(const std::string& path, unsigned int recursion); + void RefreshGameDirectory(); QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; + QFileSystemWatcher watcher; }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index a15f06c5f..3c11b6dd1 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -16,8 +16,8 @@ #include "video_core/utils.h" /** - * Gets game icon from SMDH - * @param sdmh SMDH data + * Gets the game icon from SMDH data. + * @param smdh SMDH data * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap game icon */ @@ -42,8 +42,8 @@ static QPixmap GetDefaultIcon(bool large) { } /** - * Gets the short game title fromn SMDH - * @param sdmh SMDH data + * Gets the short game title from SMDH data. + * @param smdh SMDH data * @param language title language * @return QString short title */ diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 46f48c2d8..a4ccc193b 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -29,6 +29,8 @@ void RegisterHotkey(const QString& group, const QString& action, /** * Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots. * + * @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"). * @param widget Parent widget of the returned QShortcut. * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut * will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent. diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index f765c0147..b17ed6968 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -14,8 +14,8 @@ #include <QtGui> #include <QtWidgets> #include "citra_qt/bootmanager.h" -#include "citra_qt/config.h" -#include "citra_qt/configure_dialog.h" +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/debugger/callstack.h" #include "citra_qt/debugger/disassembler.h" #include "citra_qt/debugger/graphics/graphics.h" @@ -54,28 +54,30 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { Pica::g_debug_context = Pica::DebugContext::Construct(); - + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); InitializeWidgets(); - InitializeDebugMenuActions(); + InitializeDebugWidgets(); InitializeRecentFileMenuActions(); InitializeHotkeys(); SetDefaultUIGeometry(); RestoreUIState(); + ConnectMenuEvents(); ConnectWidgetEvents(); - setWindowTitle(QString("Citra | %1-%2").arg(Common::g_scm_branch, Common::g_scm_desc)); + setWindowTitle(QString("Citra %1| %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); QStringList args = QApplication::arguments(); if (args.length() >= 2) { - BootGame(args[1].toStdString()); + BootGame(args[1]); } } @@ -94,73 +96,100 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(); ui.horizontalLayout->addWidget(game_list); - profilerWidget = new ProfilerWidget(this); - addDockWidget(Qt::BottomDockWidgetArea, profilerWidget); - profilerWidget->hide(); + // Create status bar + emu_speed_label = new QLabel(); + emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " + "indicate emulation is running faster or slower than a 3DS.")); + game_fps_label = new QLabel(); + game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " + "This will vary from game to game and scene to scene.")); + emu_frametime_label = new QLabel(); + emu_frametime_label->setToolTip( + tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " + "full-speed emulation this should be at most 16.67 ms.")); + + for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + label->setVisible(false); + label->setFrameStyle(QFrame::NoFrame); + label->setContentsMargins(4, 0, 4, 0); + statusBar()->addPermanentWidget(label); + } + statusBar()->setVisible(true); + setStyleSheet("QStatusBar::item{border: none;}"); +} + +void GMainWindow::InitializeDebugWidgets() { + connect(ui.action_Create_Pica_Surface_Viewer, &QAction::triggered, this, + &GMainWindow::OnCreateGraphicsSurfaceViewer); + + QMenu* debug_menu = ui.menu_View_Debugging; #if MICROPROFILE_ENABLED microProfileDialog = new MicroProfileDialog(this); microProfileDialog->hide(); + debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif disasmWidget = new DisassemblerWidget(this, emu_thread.get()); addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); disasmWidget->hide(); + debug_menu->addAction(disasmWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, disasmWidget, + &DisassemblerWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, disasmWidget, + &DisassemblerWidget::OnEmulationStopping); registersWidget = new RegistersWidget(this); addDockWidget(Qt::RightDockWidgetArea, registersWidget); registersWidget->hide(); + debug_menu->addAction(registersWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, registersWidget, + &RegistersWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, registersWidget, + &RegistersWidget::OnEmulationStopping); callstackWidget = new CallstackWidget(this); addDockWidget(Qt::RightDockWidgetArea, callstackWidget); callstackWidget->hide(); + debug_menu->addAction(callstackWidget->toggleViewAction()); graphicsWidget = new GPUCommandStreamWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsWidget); graphicsWidget->hide(); + debug_menu->addAction(graphicsWidget->toggleViewAction()); graphicsCommandsWidget = new GPUCommandListWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); graphicsVertexShaderWidget->hide(); + debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); graphicsTracingWidget->hide(); + debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget, + &GraphicsTracingWidget::OnEmulationStopping); waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); -} - -void GMainWindow::InitializeDebugMenuActions() { - auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica Surface Viewer"), this); - connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, - SLOT(OnCreateGraphicsSurfaceViewer())); - - QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); - debug_menu->addAction(graphicsSurfaceViewerAction); - debug_menu->addSeparator(); - debug_menu->addAction(profilerWidget->toggleViewAction()); -#if MICROPROFILE_ENABLED - debug_menu->addAction(microProfileDialog->toggleViewAction()); -#endif - debug_menu->addAction(disasmWidget->toggleViewAction()); - debug_menu->addAction(registersWidget->toggleViewAction()); - debug_menu->addAction(callstackWidget->toggleViewAction()); - debug_menu->addAction(graphicsWidget->toggleViewAction()); - debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); - debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); - debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); debug_menu->addAction(waitTreeWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, waitTreeWidget, + &WaitTreeWidget::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, + &WaitTreeWidget::OnEmulationStopping); } void GMainWindow::InitializeRecentFileMenuActions() { @@ -215,41 +244,46 @@ void GMainWindow::RestoreUIState() { ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode); ToggleWindowMode(); - ui.actionDisplay_widget_title_bars->setChecked(UISettings::values.display_titlebar); - OnDisplayTitleBars(ui.actionDisplay_widget_title_bars->isChecked()); + ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); + OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); + + ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); + statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); } void GMainWindow::ConnectWidgetEvents() { - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), - Qt::DirectConnection); + connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, - SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); - connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); - connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), - Qt::DirectConnection); - connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap())); - connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, - SLOT(OnMenuSelectGameListRoot())); - connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame())); - connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame())); - connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); - connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); - - connect(this, SIGNAL(EmulationStarting(EmuThread*)), disasmWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), disasmWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), registersWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), registersWidget, SLOT(OnEmulationStopping())); + SLOT(OnGameListOpenSaveFolder(u64))); + connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, SLOT(OnEmulationStarting(EmuThread*))); connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), graphicsTracingWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), graphicsTracingWidget, SLOT(OnEmulationStopping())); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), waitTreeWidget, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), waitTreeWidget, SLOT(OnEmulationStopping())); + + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); +} + +void GMainWindow::ConnectMenuEvents() { + // File + connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); + connect(ui.action_Load_Symbol_Map, &QAction::triggered, this, + &GMainWindow::OnMenuLoadSymbolMap); + connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, + &GMainWindow::OnMenuSelectGameListRoot); + connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); + + // Emulation + connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); + connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); + connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); + connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + + // View + connect(ui.action_Single_Window_Mode, &QAction::triggered, this, + &GMainWindow::ToggleWindowMode); + connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, + &GMainWindow::OnDisplayTitleBars); + connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -272,7 +306,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } -bool GMainWindow::LoadROM(const std::string& filename) { +bool GMainWindow::LoadROM(const QString& filename) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -290,12 +324,13 @@ bool GMainWindow::LoadROM(const std::string& filename) { Core::System& system{Core::System::GetInstance()}; - const Core::System::ResultStatus result{system.Load(render_window, filename)}; + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; if (result != Core::System::ResultStatus::Success) { switch (result) { case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str()); + LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", + filename.toStdString().c_str()); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("The ROM format is not supported.")); break; @@ -335,7 +370,7 @@ bool GMainWindow::LoadROM(const std::string& filename) { return true; } -void GMainWindow::BootGame(const std::string& filename) { +void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Citra starting..."); StoreRecentFile(filename); // Put the filename on top of the list @@ -374,6 +409,8 @@ void GMainWindow::BootGame(const std::string& filename) { if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } + status_bar_update_timer.start(2000); + render_window->show(); render_window->setFocus(); @@ -408,11 +445,17 @@ void GMainWindow::ShutdownGame() { render_window->hide(); game_list->show(); + // Disable status bar updates + status_bar_update_timer.stop(); + emu_speed_label->setVisible(false); + game_fps_label->setVisible(false); + emu_frametime_label->setVisible(false); + emulation_running = false; } -void GMainWindow::StoreRecentFile(const std::string& filename) { - UISettings::values.recent_files.prepend(QString::fromStdString(filename)); +void GMainWindow::StoreRecentFile(const QString& filename) { + UISettings::values.recent_files.prepend(filename); UISettings::values.recent_files.removeDuplicates(); while (UISettings::values.recent_files.size() > max_recent_files_item) { UISettings::values.recent_files.removeLast(); @@ -447,7 +490,7 @@ void GMainWindow::UpdateRecentFiles() { } void GMainWindow::OnGameListLoadFile(QString game_path) { - BootGame(game_path.toStdString()); + BootGame(game_path); } void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { @@ -466,19 +509,25 @@ void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { } void GMainWindow::OnMenuLoadFile() { - QString filename = - QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, - tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)")); + QString extensions; + for (const auto& piece : game_list->supported_file_extensions) + extensions += "*." + piece + " "; + + QString file_filter = tr("3DS Executable") + " (" + extensions + ")"; + file_filter += ";;" + tr("All Files (*.*)"); + + QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), + UISettings::values.roms_path, file_filter); if (!filename.isEmpty()) { UISettings::values.roms_path = QFileInfo(filename).path(); - BootGame(filename.toStdString()); + BootGame(filename); } } void GMainWindow::OnMenuLoadSymbolMap() { QString filename = QFileDialog::getOpenFileName( - this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol map (*)")); + this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol Map (*.*)")); if (!filename.isEmpty()) { UISettings::values.symbols_path = QFileInfo(filename).path(); @@ -501,7 +550,7 @@ void GMainWindow::OnMenuRecentFile() { QString filename = action->data().toString(); QFileInfo file_info(filename); if (file_info.exists()) { - BootGame(filename.toStdString()); + BootGame(filename); } else { // Display an error message and remove the file from the list. QMessageBox::information(this, tr("File not found"), @@ -564,7 +613,6 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); - render_window->ReloadSetKeymaps(); config->Save(); } } @@ -581,6 +629,23 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { graphicsSurfaceViewerWidget->show(); } +void GMainWindow::UpdateStatusBar() { + if (emu_thread == nullptr) { + status_bar_update_timer.stop(); + return; + } + + auto results = Core::System::GetInstance().GetAndResetPerfStats(); + + emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); + emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); + + emu_speed_label->setVisible(true); + game_fps_label->setVisible(true); + emu_frametime_label->setVisible(true); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; @@ -605,7 +670,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.display_titlebar = ui.actionDisplay_widget_title_bars->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); UISettings::values.first_start = false; game_list->SaveInterfaceLayout(); @@ -620,6 +686,40 @@ void GMainWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } +static bool IsSingleFileDropEvent(QDropEvent* event) { + const QMimeData* mimeData = event->mimeData(); + return mimeData->hasUrls() && mimeData->urls().length() == 1; +} + +void GMainWindow::dropEvent(QDropEvent* event) { + if (IsSingleFileDropEvent(event) && ConfirmChangeGame()) { + const QMimeData* mimeData = event->mimeData(); + QString filename = mimeData->urls().at(0).toLocalFile(); + BootGame(filename); + } +} + +void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { + if (IsSingleFileDropEvent(event)) { + event->acceptProposedAction(); + } +} + +void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { + event->acceptProposedAction(); +} + +bool GMainWindow::ConfirmChangeGame() { + if (emu_thread == nullptr) + return true; + + auto answer = QMessageBox::question( + this, tr("Citra"), + tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + return answer != QMessageBox::No; +} + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index a2fd45c47..ec841eaa5 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -64,7 +64,7 @@ signals: private: void InitializeWidgets(); - void InitializeDebugMenuActions(); + void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); void InitializeHotkeys(); @@ -72,15 +72,10 @@ private: void RestoreUIState(); void ConnectWidgetEvents(); + void ConnectMenuEvents(); - /** - * Initializes the emulation system. - * @param system_mode The system mode with which to intialize the kernel. - * @returns Whether the system was properly initialized. - */ - bool InitializeSystem(u32 system_mode); - bool LoadROM(const std::string& filename); - void BootGame(const std::string& filename); + bool LoadROM(const QString& filename); + void BootGame(const QString& filename); void ShutdownGame(); /** @@ -94,7 +89,7 @@ private: * * @param filename the filename to store */ - void StoreRecentFile(const std::string& filename); + void StoreRecentFile(const QString& filename); /** * Updates the recent files menu. @@ -110,6 +105,7 @@ private: * @return true if the user confirmed */ bool ConfirmClose(); + bool ConfirmChangeGame(); void closeEvent(QCloseEvent* event) override; private slots: @@ -131,17 +127,26 @@ private slots: void OnCreateGraphicsSurfaceViewer(); private: + void UpdateStatusBar(); + Ui::MainWindow ui; GRenderWindow* render_window; GameList* game_list; + // Status bar elements + QLabel* emu_speed_label = nullptr; + QLabel* game_fps_label = nullptr; + QLabel* emu_frametime_label = nullptr; + QTimer status_bar_update_timer; + std::unique_ptr<Config> config; // Whether emulation is currently running in Citra. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; DisassemblerWidget* disasmWidget; @@ -155,6 +160,11 @@ private: WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + +protected: + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; }; #endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index adfa3689e..47dbb6ef7 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -79,8 +79,17 @@ <property name="title"> <string>&View</string> </property> + <widget class="QMenu" name="menu_View_Debugging"> + <property name="title"> + <string>Debugging</string> + </property> + <addaction name="action_Create_Pica_Surface_Viewer"/> + <addaction name="separator"/> + </widget> <addaction name="action_Single_Window_Mode"/> - <addaction name="actionDisplay_widget_title_bars"/> + <addaction name="action_Display_Dock_Widget_Headers"/> + <addaction name="action_Show_Status_Bar"/> + <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -93,7 +102,6 @@ <addaction name="menu_View"/> <addaction name="menu_Help"/> </widget> - <widget class="QStatusBar" name="statusbar"/> <action name="action_Load_File"> <property name="text"> <string>Load File...</string> @@ -151,7 +159,7 @@ <string>Configure...</string> </property> </action> - <action name="actionDisplay_widget_title_bars"> + <action name="action_Display_Dock_Widget_Headers"> <property name="checkable"> <bool>true</bool> </property> @@ -159,6 +167,14 @@ <string>Display Dock Widget Headers</string> </property> </action> + <action name="action_Show_Status_Bar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Show Status Bar</string> + </property> + </action> <action name="action_Select_Game_List_Root"> <property name="text"> <string>Select Game Directory...</string> @@ -167,44 +183,11 @@ <string>Selects a folder to display in the game list</string> </property> </action> + <action name="action_Create_Pica_Surface_Viewer"> + <property name="text"> + <string>Create Pica Surface Viewer</string> + </property> + </action> </widget> <resources/> - <connections> - <connection> - <sender>action_Exit</sender> - <signal>triggered()</signal> - <receiver>MainWindow</receiver> - <slot>close()</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>367</x> - <y>314</y> - </hint> - </hints> - </connection> - <connection> - <sender>actionDisplay_widget_title_bars</sender> - <signal>triggered(bool)</signal> - <receiver>MainWindow</receiver> - <slot>OnDisplayTitleBars(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>-1</x> - <y>-1</y> - </hint> - <hint type="destinationlabel"> - <x>540</x> - <y>364</y> - </hint> - </hints> - </connection> - </connections> - <slots> - <slot>OnConfigure()</slot> - <slot>OnDisplayTitleBars(bool)</slot> - </slots> </ui> diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index ed7fdff7e..6408ece2b 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values { bool single_window_mode; bool display_titlebar; + bool show_status_bar; bool confirm_before_closing; bool first_start; |