diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index c74a111e92..9e3c5eae20 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -120,6 +120,9 @@ target_sources(pcsx2-qt PRIVATE Tools/InputRecording/NewInputRecordingDlg.cpp Tools/InputRecording/NewInputRecordingDlg.h Tools/InputRecording/NewInputRecordingDlg.ui + Tools/InputRecording/InputRecordingViewer.cpp + Tools/InputRecording/InputRecordingViewer.h + Tools/InputRecording/InputRecordingViewer.ui resources/resources.qrc ) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 37dfb00492..33bf02be48 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -37,6 +37,7 @@ #include "pcsx2/HostSettings.h" #include "pcsx2/PerformanceMetrics.h" #include "pcsx2/Recording/InputRecording.h" +#include "pcsx2/Recording/InputRecordingControls.h" #include "AboutDialog.h" #include "AutoUpdaterDialog.h" @@ -52,6 +53,7 @@ #include "Settings/InterfaceSettingsWidget.h" #include "SettingWidgetBinder.h" #include "svnrev.h" +#include "Tools/InputRecording/InputRecordingViewer.h" #include "Tools/InputRecording/NewInputRecordingDlg.h" #ifdef _WIN32 @@ -386,6 +388,8 @@ void MainWindow::connectSignals() connect(m_ui.actionInputRecStop, &QAction::triggered, this, &MainWindow::onInputRecStopActionTriggered); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecConsoleLogs, "Logging", "EnableInputRecordingLogs", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecControllerLogs, "Logging", "EnableControllerLogs", false); + connect(m_ui.actionInputRecControllerLogs, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged); + connect(m_ui.actionInputRecOpenViewer, &QAction::triggered, this, &MainWindow::onInputRecOpenViewer); // These need to be queued connections to stop crashing due to menus opening/closing and switching focus. connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress); @@ -1643,11 +1647,13 @@ void MainWindow::onInputRecNewActionTriggered() if (result == QDialog::Accepted) { - if (g_InputRecording.Create( + if (g_InputRecording.create( dlg.getFilePath(), dlg.getInputRecType() == InputRecording::Type::FROM_SAVESTATE, dlg.getAuthorName())) { + m_ui.actionInputRecNew->setEnabled(false); + m_ui.actionInputRecStop->setEnabled(true); return; } } @@ -1658,14 +1664,14 @@ void MainWindow::onInputRecNewActionTriggered() } } -#include "pcsx2/Recording/InputRecordingControls.h" - void MainWindow::onInputRecPlayActionTriggered() { const bool wasPaused = s_vm_paused; if (!wasPaused) - g_InputRecordingControls.PauseImmediately(); + { + VMManager::SetPaused(true); + } QFileDialog dialog(this); dialog.setFileMode(QFileDialog::ExistingFile); @@ -1676,30 +1682,37 @@ void MainWindow::onInputRecPlayActionTriggered() { fileNames = dialog.selectedFiles(); } - - if (fileNames.length() > 0) + else { - if (g_InputRecording.IsActive()) - { - g_InputRecording.Stop(); - } - if (g_InputRecording.Play(fileNames.first().toStdString())) + if (!wasPaused) { + VMManager::SetPaused(false); return; } } - if (!wasPaused) + if (fileNames.length() > 0) { - g_InputRecordingControls.Resume(); + if (g_InputRecording.isActive()) + { + g_InputRecording.stop(); + m_ui.actionInputRecStop->setEnabled(false); + } + if (g_InputRecording.play(fileNames.first().toStdString())) + { + m_ui.actionInputRecStop->setEnabled(true); + return; + } } } void MainWindow::onInputRecStopActionTriggered() { - if (g_InputRecording.IsActive()) + if (g_InputRecording.isActive()) { - g_InputRecording.Stop(); + g_InputRecording.stop(); + m_ui.actionInputRecNew->setEnabled(true); + m_ui.actionInputRecStop->setEnabled(false); } } @@ -1708,6 +1721,32 @@ void MainWindow::onInputRecOpenSettingsTriggered() // TODO - Vaser - Implement } +InputRecordingViewer* MainWindow::getInputRecordingViewer() +{ + if (!m_input_recording_viewer) + { + m_input_recording_viewer = new InputRecordingViewer(this); + } + + return m_input_recording_viewer; +} + +void MainWindow::updateInputRecordingActions(bool started) +{ + m_ui.actionInputRecNew->setEnabled(started); + m_ui.actionInputRecPlay->setEnabled(started); +} + +void MainWindow::onInputRecOpenViewer() +{ + InputRecordingViewer* viewer = getInputRecordingViewer(); + if (!viewer->isVisible()) + { + viewer->show(); + } +} + + void MainWindow::onVMStarting() { s_vm_valid = true; @@ -1725,6 +1764,7 @@ void MainWindow::onVMStarted() updateEmulationActions(true, true); updateWindowTitle(); updateStatusBarWidgetVisibility(); + updateInputRecordingActions(true); } void MainWindow::onVMPaused() @@ -1778,6 +1818,7 @@ void MainWindow::onVMStopped() updateWindowTitle(); updateWindowState(); updateStatusBarWidgetVisibility(); + updateInputRecordingActions(false); if (m_display_widget) { diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index fd8c557b5c..a0ea86fe7e 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -22,6 +22,7 @@ #include #include +#include "Tools/InputRecording/InputRecordingViewer.h" #include "Settings/ControllerSettingsDialog.h" #include "Settings/SettingsDialog.h" #include "ui_MainWindow.h" @@ -163,6 +164,7 @@ private Q_SLOTS: void onInputRecPlayActionTriggered(); void onInputRecStopActionTriggered(); void onInputRecOpenSettingsTriggered(); + void onInputRecOpenViewer(); void onVMStarting(); void onVMStarted(); @@ -224,6 +226,9 @@ private: SettingsDialog* getSettingsDialog(); void doSettings(const char* category = nullptr); + InputRecordingViewer* getInputRecordingViewer(); + void updateInputRecordingActions(bool started); + ControllerSettingsDialog* getControllerSettingsDialog(); void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count); @@ -250,6 +255,7 @@ private: DisplayContainer* m_display_container = nullptr; SettingsDialog* m_settings_dialog = nullptr; + InputRecordingViewer* m_input_recording_viewer = nullptr; ControllerSettingsDialog* m_controller_settings_dialog = nullptr; AutoUpdaterDialog* m_auto_updater_dialog = nullptr; diff --git a/pcsx2-qt/MainWindow.ui b/pcsx2-qt/MainWindow.ui index 43b176a3d3..0aa4d4e0a0 100644 --- a/pcsx2-qt/MainWindow.ui +++ b/pcsx2-qt/MainWindow.ui @@ -176,6 +176,7 @@ + @@ -864,6 +865,11 @@ Show Advanced Settings + + + Recording Viewer + + diff --git a/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.cpp b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.cpp new file mode 100644 index 0000000000..50850b3fb9 --- /dev/null +++ b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.cpp @@ -0,0 +1,91 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "InputRecordingViewer.h" + +#include "QtUtils.h" +#include +#include +#include + +// TODO - for now this uses a very naive implementation that fills the entire table +// this needs to be replaced with a lazy-loading QTableView implementation +// +// For now, especially for just debugging input recording issues, its good enough! + +InputRecordingViewer::InputRecordingViewer(QWidget* parent) + : QMainWindow(parent) +{ + m_ui.setupUi(this); + + m_ui.tableWidget->setSelectionMode(QAbstractItemView::NoSelection); + + connect(m_ui.actionOpen, &QAction::triggered, this, &InputRecordingViewer::openFile); +} + +void InputRecordingViewer::loadTable() +{ + // TODO - only port 1 for now + auto data = m_file.bulkReadPadData(0, m_file.getTotalFrames(), 0); + m_ui.tableWidget->setRowCount(data.size()); + auto headers = QStringList({"Left Analog", "Right Analog", "Cross", "Square", "Triangle", "Circle", "L1", "R1", "L2", "R2", "⬇️", "➡️", "⬆️", "⬅️", "L3", "R3", "Select", "Start"}); + m_ui.tableWidget->setColumnCount(headers.length()); + m_ui.tableWidget->setHorizontalHeaderLabels(headers); + + int frameNum = 0; + for (auto& const frame : data) + { + // TODO - disgusting, clean it up + m_ui.tableWidget->setItem(frameNum, 0, new QTableWidgetItem(tr("%1 %2").arg(frame.leftAnalogX).arg(frame.leftAnalogY))); + m_ui.tableWidget->setItem(frameNum, 1, new QTableWidgetItem(tr("%1 %2").arg(frame.rightAnalogX).arg(frame.rightAnalogY))); + m_ui.tableWidget->setItem(frameNum, 2, new QTableWidgetItem(tr("%1 [%2]").arg(frame.crossPressed).arg(frame.crossPressure))); + m_ui.tableWidget->setItem(frameNum, 3, new QTableWidgetItem(tr("%1 [%2]").arg(frame.squarePressed).arg(frame.squarePressure))); + m_ui.tableWidget->setItem(frameNum, 4, new QTableWidgetItem(tr("%1 [%2]").arg(frame.trianglePressed).arg(frame.trianglePressure))); + m_ui.tableWidget->setItem(frameNum, 5, new QTableWidgetItem(tr("%1 [%2]").arg(frame.circlePressed).arg(frame.circlePressure))); + m_ui.tableWidget->setItem(frameNum, 6, new QTableWidgetItem(tr("%1 [%2]").arg(frame.l1Pressed).arg(frame.l1Pressure))); + m_ui.tableWidget->setItem(frameNum, 7, new QTableWidgetItem(tr("%1 [%2]").arg(frame.l2Pressed).arg(frame.l2Pressure))); + m_ui.tableWidget->setItem(frameNum, 8, new QTableWidgetItem(tr("%1 [%2]").arg(frame.r1Pressed).arg(frame.r1Pressure))); + m_ui.tableWidget->setItem(frameNum, 9, new QTableWidgetItem(tr("%1 [%2]").arg(frame.r1Pressed).arg(frame.r2Pressure))); + m_ui.tableWidget->setItem(frameNum, 10, new QTableWidgetItem(tr("%1 [%2]").arg(frame.downPressed).arg(frame.downPressure))); + m_ui.tableWidget->setItem(frameNum, 11, new QTableWidgetItem(tr("%1 [%2]").arg(frame.rightPressed).arg(frame.rightPressure))); + m_ui.tableWidget->setItem(frameNum, 12, new QTableWidgetItem(tr("%1 [%2]").arg(frame.upPressed).arg(frame.upPressure))); + m_ui.tableWidget->setItem(frameNum, 13, new QTableWidgetItem(tr("%1 [%2]").arg(frame.leftPressed).arg(frame.leftPressure))); + m_ui.tableWidget->setItem(frameNum, 14, new QTableWidgetItem(tr("%1").arg(frame.l3))); + m_ui.tableWidget->setItem(frameNum, 15, new QTableWidgetItem(tr("%1").arg(frame.r3))); + m_ui.tableWidget->setItem(frameNum, 16, new QTableWidgetItem(tr("%1").arg(frame.select))); + m_ui.tableWidget->setItem(frameNum, 17, new QTableWidgetItem(tr("%1").arg(frame.start))); + frameNum++; + } +} + +void InputRecordingViewer::openFile() { + QFileDialog dialog(this); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setWindowTitle("Select a File"); + dialog.setNameFilter(tr("Input Recording Files (*.p2m2)")); + QStringList fileNames; + if (dialog.exec()) + { + fileNames = dialog.selectedFiles(); + } + if (!fileNames.isEmpty()) + { + std::string fileName = fileNames.first().toStdString(); + m_file.OpenExisting(fileName); + loadTable(); + } +} \ No newline at end of file diff --git a/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.h b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.h new file mode 100644 index 0000000000..ae974fbd29 --- /dev/null +++ b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.h @@ -0,0 +1,39 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include "ui_InputRecordingViewer.h" + +#include "pcsx2/Recording/InputRecordingFile.h" + +class InputRecordingViewer final : public QMainWindow +{ + Q_OBJECT + +public: + explicit InputRecordingViewer(QWidget* parent = nullptr); + ~InputRecordingViewer() = default; + +private Q_SLOTS: + void openFile(); + +private: + Ui::InputRecordingViewer m_ui; + + InputRecordingFile m_file; + + void loadTable(); +}; diff --git a/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.ui b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.ui new file mode 100644 index 0000000000..99c678670e --- /dev/null +++ b/pcsx2-qt/Tools/InputRecording/InputRecordingViewer.ui @@ -0,0 +1,65 @@ + + + InputRecordingViewer + + + + 0 + 0 + 800 + 600 + + + + Input Recording Viewer + + + + true + + + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + Edit + + + + + View + + + + + + + + + + Open + + + + + + + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 4d9356c834..4cce514e49 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -137,6 +137,7 @@ + @@ -209,6 +210,7 @@ + @@ -264,6 +266,7 @@ + NotUsing @@ -353,6 +356,9 @@ Document + + Document + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 46350ab406..88777df26c 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -211,6 +211,9 @@ moc + + moc + Tools\Input Recording @@ -244,6 +247,9 @@ Settings + + Tools\Input Recording + @@ -263,6 +269,9 @@ Settings + + Tools\Input Recording + @@ -450,6 +459,9 @@ Settings + + Tools\Input Recording + diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index 2128848783..3346bd29ed 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -238,6 +238,9 @@ {03ba2aa7-2cd9-48cb-93c6-fc93d5bdc938} + + {78c9db9c-9c7c-4385-90e7-9fa71b922f60} + @@ -1263,12 +1266,12 @@ Tools\Input Recording - - Tools\Input Recording - Tools\Input Recording + + Tools\Input Recording\Utilities + Host @@ -2128,12 +2131,12 @@ Tools\Input Recording - - Tools\Input Recording - Tools\Input Recording + + Tools\Input Recording\Utilities + Host