diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp index c51708980e..0b8a122b55 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.cpp +++ b/Source/Core/DolphinQt2/GameList/GameList.cpp @@ -74,6 +74,10 @@ void GameList::MakeListView() m_list->setWordWrap(false); connect(m_list, &QTableView::customContextMenuRequested, this, &GameList::ShowContextMenu); + connect(m_list->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection&, const QItemSelection&) { + emit SelectionChanged(GetSelectedGame()); + }); m_list->setColumnHidden(GameListModel::COL_PLATFORM, !SConfig::GetInstance().m_showSystemColumn); m_list->setColumnHidden(GameListModel::COL_ID, !SConfig::GetInstance().m_showIDColumn); @@ -137,6 +141,10 @@ void GameList::MakeGridView() m_grid->setContextMenuPolicy(Qt::CustomContextMenu); m_grid->setFrameStyle(QFrame::NoFrame); connect(m_grid, &QTableView::customContextMenuRequested, this, &GameList::ShowContextMenu); + connect(m_grid->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection&, const QItemSelection&) { + emit SelectionChanged(GetSelectedGame()); + }); } void GameList::ShowContextMenu(const QPoint&) diff --git a/Source/Core/DolphinQt2/GameList/GameList.h b/Source/Core/DolphinQt2/GameList/GameList.h index e99b3147d6..e01d32f0ad 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.h +++ b/Source/Core/DolphinQt2/GameList/GameList.h @@ -32,6 +32,7 @@ signals: void EmulationStarted(); void EmulationStopped(); void NetPlayHost(const QString& game_id); + void SelectionChanged(QSharedPointer game_file); private: void ShowContextMenu(const QPoint&); diff --git a/Source/Core/DolphinQt2/HotkeyScheduler.cpp b/Source/Core/DolphinQt2/HotkeyScheduler.cpp index e938663826..e3aa7750c9 100644 --- a/Source/Core/DolphinQt2/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt2/HotkeyScheduler.cpp @@ -162,6 +162,16 @@ void HotkeyScheduler::Run() auto& settings = Settings::Instance(); + // Recording + if (IsHotkey(HK_START_RECORDING)) + emit StartRecording(); + + if (IsHotkey(HK_EXPORT_RECORDING)) + emit ExportRecording(); + + if (IsHotkey(HK_READ_ONLY_MODE)) + emit ToggleReadOnlyMode(); + // Volume if (IsHotkey(HK_VOLUME_DOWN)) settings.DecreaseVolume(3); diff --git a/Source/Core/DolphinQt2/HotkeyScheduler.h b/Source/Core/DolphinQt2/HotkeyScheduler.h index 2deebbce16..08ff20b725 100644 --- a/Source/Core/DolphinQt2/HotkeyScheduler.h +++ b/Source/Core/DolphinQt2/HotkeyScheduler.h @@ -28,6 +28,9 @@ signals: void SetStateSlotHotkey(int slot); void StateLoadSlotHotkey(); void StateSaveSlotHotkey(); + void StartRecording(); + void ExportRecording(); + void ToggleReadOnlyMode(); private: void Run(); diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index a17d5c50da..abb2ac38d2 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -211,6 +211,12 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog); + // Movie + connect(m_menu_bar, &MenuBar::PlayRecording, this, &MainWindow::OnPlayRecording); + connect(m_menu_bar, &MenuBar::StartRecording, this, &MainWindow::OnStartRecording); + connect(m_menu_bar, &MenuBar::StopRecording, this, &MainWindow::OnStopRecording); + connect(m_menu_bar, &MenuBar::ExportRecording, this, &MainWindow::OnExportRecording); + // View connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView); connect(m_menu_bar, &MenuBar::ShowGrid, m_game_list, &GameList::SetGridView); @@ -227,6 +233,9 @@ void MainWindow::ConnectMenuBar() connect(this, &MainWindow::EmulationStarted, m_menu_bar, &MenuBar::EmulationStarted); connect(this, &MainWindow::EmulationPaused, m_menu_bar, &MenuBar::EmulationPaused); connect(this, &MainWindow::EmulationStopped, m_menu_bar, &MenuBar::EmulationStopped); + connect(m_game_list, &GameList::SelectionChanged, m_menu_bar, &MenuBar::SelectionChanged); + connect(this, &MainWindow::ReadOnlyModeChanged, m_menu_bar, &MenuBar::ReadOnlyModeChanged); + connect(this, &MainWindow::RecordingStatusChanged, m_menu_bar, &MenuBar::RecordingStatusChanged); connect(this, &MainWindow::EmulationStarted, this, [=]() { m_controllers_window->OnEmulationStateChanged(true); }); @@ -248,6 +257,16 @@ void MainWindow::ConnectHotkeys() &MainWindow::StateSaveSlot); connect(m_hotkey_scheduler, &HotkeyScheduler::SetStateSlotHotkey, this, &MainWindow::SetStateSlot); + + connect(m_hotkey_scheduler, &HotkeyScheduler::StartRecording, this, + &MainWindow::OnStartRecording); + connect(m_hotkey_scheduler, &HotkeyScheduler::ExportRecording, this, + &MainWindow::OnExportRecording); + connect(m_hotkey_scheduler, &HotkeyScheduler::ToggleReadOnlyMode, [this] { + bool read_only = !Movie::IsReadOnly(); + Movie::SetReadOnly(read_only); + emit ReadOnlyModeChanged(read_only); + }); } void MainWindow::ConnectToolBar() @@ -883,3 +902,89 @@ void MainWindow::OnImportNANDBackup() m_menu_bar->UpdateToolsMenu(Core::IsRunning()); } + +void MainWindow::OnPlayRecording() +{ + QString dtm_file = QFileDialog::getOpenFileName(this, tr("Select The Recording File"), QString(), + tr("Dolphin TAS Movies (*.dtm)")); + + if (dtm_file.isEmpty()) + return; + + if (!Movie::IsReadOnly()) + { + // let's make the read-only flag consistent at the start of a movie. + Movie::SetReadOnly(true); + emit ReadOnlyModeChanged(true); + } + + if (Movie::PlayInput(dtm_file.toStdString())) + { + emit RecordingStatusChanged(true); + + Play(); + } +} + +void MainWindow::OnStartRecording() +{ + if ((!Core::IsRunningAndStarted() && Core::IsRunning()) || Movie::IsRecordingInput() || + Movie::IsPlayingInput()) + return; + + if (Movie::IsReadOnly()) + { + // The user just chose to record a movie, so that should take precedence + Movie::SetReadOnly(false); + emit ReadOnlyModeChanged(true); + } + + int controllers = 0; + + for (int i = 0; i < 4; i++) + { + if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) + controllers |= (1 << i); + + if (g_wiimote_sources[i] != WIIMOTE_SRC_NONE) + controllers |= (1 << (i + 4)); + } + + if (Movie::BeginRecordingInput(controllers)) + { + emit RecordingStatusChanged(true); + + if (!Core::IsRunning()) + Play(); + } +} + +void MainWindow::OnStopRecording() +{ + if (Movie::IsRecordingInput()) + OnExportRecording(); + + Movie::EndPlayInput(false); + emit RecordingStatusChanged(true); +} + +void MainWindow::OnExportRecording() +{ + bool was_paused = Core::GetState() == Core::State::Paused; + + if (was_paused) + Core::SetState(Core::State::Paused); + + QString dtm_file = QFileDialog::getSaveFileName(this, tr("Select The Recording File"), QString(), + tr("Dolphin TAS Movies (*.dtm)")); + + if (was_paused) + Core::SetState(Core::State::Running); + + if (dtm_file.isEmpty()) + return; + + Core::SetState(Core::State::Running); + + Movie::SaveRecording(dtm_file.toStdString()); +} diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 96efeef45b..154bd7b75a 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -43,6 +43,8 @@ signals: void EmulationStarted(); void EmulationPaused(); void EmulationStopped(); + void ReadOnlyModeChanged(bool read_only); + void RecordingStatusChanged(bool recording); private: void Open(); @@ -105,6 +107,12 @@ private: void OnBootGameCubeIPL(DiscIO::Region region); void OnImportNANDBackup(); + + void OnPlayRecording(); + void OnStartRecording(); + void OnStopRecording(); + void OnExportRecording(); + void OnStopComplete(); void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 8a1c6ec509..996c21b024 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -15,9 +15,12 @@ #include "Common/FileUtil.h" #include "Core/CommonTitles.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "Core/HW/WiiSaveCrypted.h" +#include "Core/HW/Wiimote.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/IOS.h" +#include "Core/Movie.h" #include "Core/State.h" #include "DiscIO/NANDImporter.h" #include "DolphinQt2/AboutDialog.h" @@ -28,13 +31,17 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent) { AddFileMenu(); AddEmulationMenu(); - addMenu(tr("Movie")); + AddMovieMenu(); AddOptionsMenu(); AddToolsMenu(); AddViewMenu(); AddHelpMenu(); EmulationStopped(); + + connect(this, &MenuBar::SelectionChanged, this, &MenuBar::OnSelectionChanged); + connect(this, &MenuBar::RecordingStatusChanged, this, &MenuBar::OnRecordingStatusChanged); + connect(this, &MenuBar::ReadOnlyModeChanged, this, &MenuBar::OnReadOnlyModeChanged); } void MenuBar::EmulationStarted() @@ -51,6 +58,11 @@ void MenuBar::EmulationStarted() m_screenshot_action->setEnabled(true); m_state_load_menu->setEnabled(true); m_state_save_menu->setEnabled(true); + + // Movie + m_recording_read_only->setEnabled(true); + m_recording_play->setEnabled(false); + UpdateStateSlotMenu(); UpdateToolsMenu(true); } @@ -75,6 +87,12 @@ void MenuBar::EmulationStopped() m_screenshot_action->setEnabled(false); m_state_load_menu->setEnabled(false); m_state_save_menu->setEnabled(false); + + // Movie + m_recording_read_only->setEnabled(false); + m_recording_stop->setEnabled(false); + m_recording_play->setEnabled(false); + UpdateStateSlotMenu(); UpdateToolsMenu(false); } @@ -391,6 +409,75 @@ void MenuBar::AddShowRegionsMenu(QMenu* view_menu) } } +void MenuBar::AddMovieMenu() +{ + auto* movie_menu = addMenu(tr("&Movie")); + m_recording_start = + movie_menu->addAction(tr("Start Recording Input"), [this] { emit StartRecording(); }); + m_recording_play = + movie_menu->addAction(tr("Play Input Recording..."), [this] { emit PlayRecording(); }); + m_recording_stop = + movie_menu->addAction(tr("Stop Playing/Recording Input"), [this] { emit StopRecording(); }); + m_recording_export = + movie_menu->addAction(tr("Export Recording..."), [this] { emit ExportRecording(); }); + + m_recording_start->setEnabled(false); + m_recording_play->setEnabled(false); + m_recording_stop->setEnabled(false); + m_recording_export->setEnabled(false); + + m_recording_read_only = movie_menu->addAction(tr("Read-Only Mode")); + m_recording_read_only->setCheckable(true); + m_recording_read_only->setChecked(Movie::IsReadOnly()); + connect(m_recording_read_only, &QAction::toggled, [](bool value) { Movie::SetReadOnly(value); }); + + movie_menu->addSeparator(); + + auto* pause_at_end = movie_menu->addAction(tr("Pause at End of Movie")); + pause_at_end->setCheckable(true); + pause_at_end->setChecked(SConfig::GetInstance().m_PauseMovie); + connect(pause_at_end, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_PauseMovie = value; }); + + auto* lag_counter = movie_menu->addAction(tr("Show Lag Counter")); + lag_counter->setCheckable(true); + lag_counter->setChecked(SConfig::GetInstance().m_ShowLag); + connect(lag_counter, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_ShowLag = value; }); + + auto* frame_counter = movie_menu->addAction(tr("Show Frame Counter")); + frame_counter->setCheckable(true); + frame_counter->setChecked(SConfig::GetInstance().m_ShowFrameCount); + connect(frame_counter, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_ShowFrameCount = value; }); + + auto* input_display = movie_menu->addAction(tr("Show Input Display")); + input_display->setCheckable(true); + input_display->setChecked(SConfig::GetInstance().m_ShowInputDisplay); + connect(frame_counter, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_ShowInputDisplay = value; }); + + auto* system_clock = movie_menu->addAction(tr("Show System Clock")); + system_clock->setCheckable(true); + system_clock->setChecked(SConfig::GetInstance().m_ShowRTC); + connect(system_clock, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_ShowRTC = value; }); + + movie_menu->addSeparator(); + + auto* dump_frames = movie_menu->addAction(tr("Dump Frames")); + dump_frames->setCheckable(true); + dump_frames->setChecked(SConfig::GetInstance().m_DumpFrames); + connect(dump_frames, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_DumpFrames = value; }); + + auto* dump_audio = movie_menu->addAction(tr("Dump Audio")); + dump_audio->setCheckable(true); + dump_audio->setChecked(SConfig::GetInstance().m_DumpAudio); + connect(dump_audio, &QAction::toggled, + [](bool value) { SConfig::GetInstance().m_DumpAudio = value; }); +} + void MenuBar::UpdateToolsMenu(bool emulation_started) { m_boot_sysmenu->setEnabled(!emulation_started); @@ -473,3 +560,22 @@ void MenuBar::NANDExtractCertificates() QMessageBox::critical(this, tr("Error"), tr("Failed to extract certificates from NAND")); } } + +void MenuBar::OnSelectionChanged(QSharedPointer game_file) +{ + bool is_null = game_file.isNull(); + + m_recording_play->setEnabled(!Core::IsRunning() && !is_null); + m_recording_start->setEnabled(!Movie::IsPlayingInput() && !is_null); +} + +void MenuBar::OnRecordingStatusChanged(bool recording) +{ + m_recording_start->setEnabled(!recording); + m_recording_stop->setEnabled(recording); +} + +void MenuBar::OnReadOnlyModeChanged(bool read_only) +{ + m_recording_read_only->setChecked(read_only); +} diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index a170441d19..760a84f917 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -14,6 +14,8 @@ namespace DiscIO enum class Region; }; +#include "DolphinQt2/GameList/GameFile.h" + class MenuBar final : public QMenuBar { Q_OBJECT @@ -75,6 +77,16 @@ signals: void ShowAboutDialog(); + // Movie + void PlayRecording(); + void StartRecording(); + void StopRecording(); + void ExportRecording(); + + void SelectionChanged(QSharedPointer game_file); + void RecordingStatusChanged(bool recording); + void ReadOnlyModeChanged(bool read_only); + private: void AddFileMenu(); @@ -92,12 +104,17 @@ private: void AddOptionsMenu(); void AddToolsMenu(); void AddHelpMenu(); + void AddMovieMenu(); void InstallWAD(); void ImportWiiSave(); void ExportWiiSaves(); void NANDExtractCertificates(); + void OnSelectionChanged(QSharedPointer game_file); + void OnRecordingStatusChanged(bool recording); + void OnReadOnlyModeChanged(bool read_only); + // File QAction* m_open_action; QAction* m_exit_action; @@ -127,4 +144,11 @@ private: QActionGroup* m_state_slots; QMenu* m_state_load_slots_menu; QMenu* m_state_save_slots_menu; + + // Movie + QAction* m_recording_export; + QAction* m_recording_play; + QAction* m_recording_start; + QAction* m_recording_stop; + QAction* m_recording_read_only; };