mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #5448 from zhaowenlan1779/rerecording
Implement basic rerecording features
This commit is contained in:
		
						commit
						62753e882e
					
				
					 25 changed files with 971 additions and 241 deletions
				
			
		|  | @ -128,6 +128,12 @@ add_executable(citra-qt | |||
|     main.cpp | ||||
|     main.h | ||||
|     main.ui | ||||
|     movie/movie_play_dialog.cpp | ||||
|     movie/movie_play_dialog.h | ||||
|     movie/movie_play_dialog.ui | ||||
|     movie/movie_record_dialog.cpp | ||||
|     movie/movie_record_dialog.h | ||||
|     movie/movie_record_dialog.ui | ||||
|     multiplayer/chat_room.cpp | ||||
|     multiplayer/chat_room.h | ||||
|     multiplayer/chat_room.ui | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| #include "core/3ds.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/settings.h" | ||||
| #include "input_common/keyboard.h" | ||||
| #include "input_common/main.h" | ||||
|  | @ -52,6 +53,13 @@ void EmuThread::run() { | |||
| 
 | ||||
|     emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); | ||||
| 
 | ||||
|     if (Core::System::GetInstance().frame_limiter.IsFrameAdvancing()) { | ||||
|         // Usually the loading screen is hidden after the first frame is drawn. In this case
 | ||||
|         // we hide it immediately as we need to wait for user input to start the emulation.
 | ||||
|         emit HideLoadingScreen(); | ||||
|         Core::System::GetInstance().frame_limiter.WaitOnce(); | ||||
|     } | ||||
| 
 | ||||
|     // Holds whether the cpu was running during the last iteration,
 | ||||
|     // so that the DebugModeLeft signal can be emitted before the
 | ||||
|     // next execution step.
 | ||||
|  |  | |||
|  | @ -122,6 +122,8 @@ signals: | |||
|     void ErrorThrown(Core::System::ResultStatus, std::string); | ||||
| 
 | ||||
|     void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); | ||||
| 
 | ||||
|     void HideLoadingScreen(); | ||||
| }; | ||||
| 
 | ||||
| class OpenGLWindow : public QWindow { | ||||
|  |  | |||
|  | @ -722,17 +722,17 @@ void GameList::RefreshGameDirectory() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(u64 program_id) { | ||||
|     return FindGameByProgramID(item_model->invisibleRootItem(), program_id); | ||||
| QString GameList::FindGameByProgramID(u64 program_id, int role) { | ||||
|     return FindGameByProgramID(item_model->invisibleRootItem(), program_id, role); | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_id) { | ||||
| QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_id, int role) { | ||||
|     if (current_item->type() == static_cast<int>(GameListItemType::Game) && | ||||
|         current_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { | ||||
|         return current_item->data(GameListItemPath::FullPathRole).toString(); | ||||
|         return current_item->data(role).toString(); | ||||
|     } else if (current_item->hasChildren()) { | ||||
|         for (int child_id = 0; child_id < current_item->rowCount(); child_id++) { | ||||
|             QString path = FindGameByProgramID(current_item->child(child_id, 0), program_id); | ||||
|             QString path = FindGameByProgramID(current_item->child(child_id, 0), program_id, role); | ||||
|             if (!path.isEmpty()) | ||||
|                 return path; | ||||
|         } | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ public: | |||
| 
 | ||||
|     QStandardItemModel* GetModel() const; | ||||
| 
 | ||||
|     QString FindGameByProgramID(u64 program_id); | ||||
|     QString FindGameByProgramID(u64 program_id, int role); | ||||
| 
 | ||||
|     void RefreshGameDirectory(); | ||||
| 
 | ||||
|  | @ -105,7 +105,7 @@ private: | |||
|     void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
|     void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
| 
 | ||||
|     QString FindGameByProgramID(QStandardItem* current_item, u64 program_id); | ||||
|     QString FindGameByProgramID(QStandardItem* current_item, u64 program_id, int role); | ||||
| 
 | ||||
|     GameListSearchField* search_field; | ||||
|     GMainWindow* main_window = nullptr; | ||||
|  |  | |||
|  | @ -51,6 +51,8 @@ | |||
| #include "citra_qt/hotkeys.h" | ||||
| #include "citra_qt/loading_screen.h" | ||||
| #include "citra_qt/main.h" | ||||
| #include "citra_qt/movie/movie_play_dialog.h" | ||||
| #include "citra_qt/movie/movie_record_dialog.h" | ||||
| #include "citra_qt/multiplayer/state.h" | ||||
| #include "citra_qt/qt_image_interface.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
|  | @ -174,6 +176,10 @@ GMainWindow::GMainWindow() | |||
| 
 | ||||
|     Network::Init(); | ||||
| 
 | ||||
|     Core::Movie::GetInstance().SetPlaybackCompletionCallback([this] { | ||||
|         QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted", Qt::BlockingQueuedConnection); | ||||
|     }); | ||||
| 
 | ||||
|     InitializeWidgets(); | ||||
|     InitializeDebugWidgets(); | ||||
|     InitializeRecentFileMenuActions(); | ||||
|  | @ -755,8 +761,10 @@ void GMainWindow::ConnectMenuEvents() { | |||
|     // Movie
 | ||||
|     connect(ui->action_Record_Movie, &QAction::triggered, this, &GMainWindow::OnRecordMovie); | ||||
|     connect(ui->action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie); | ||||
|     connect(ui->action_Stop_Recording_Playback, &QAction::triggered, this, | ||||
|             &GMainWindow::OnStopRecordingPlayback); | ||||
|     connect(ui->action_Close_Movie, &QAction::triggered, this, &GMainWindow::OnCloseMovie); | ||||
|     connect(ui->action_Save_Movie, &QAction::triggered, this, &GMainWindow::OnSaveMovie); | ||||
|     connect(ui->action_Movie_Read_Only_Mode, &QAction::toggled, this, | ||||
|             [this](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); }); | ||||
|     connect(ui->action_Enable_Frame_Advancing, &QAction::triggered, this, [this] { | ||||
|         if (emulation_running) { | ||||
|             Core::System::GetInstance().frame_limiter.SetFrameAdvancing( | ||||
|  | @ -1025,6 +1033,9 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|     if (movie_record_on_start) { | ||||
|         Core::Movie::GetInstance().PrepareForRecording(); | ||||
|     } | ||||
|     if (movie_playback_on_start) { | ||||
|         Core::Movie::GetInstance().PrepareForPlayback(movie_playback_path.toStdString()); | ||||
|     } | ||||
| 
 | ||||
|     // Save configurations
 | ||||
|     UpdateUISettings(); | ||||
|  | @ -1034,6 +1045,42 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|     if (!LoadROM(filename)) | ||||
|         return; | ||||
| 
 | ||||
|     // Set everything up
 | ||||
|     if (movie_record_on_start) { | ||||
|         Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString(), | ||||
|                                                   movie_record_author.toStdString()); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|         movie_record_author.clear(); | ||||
|     } | ||||
|     if (movie_playback_on_start) { | ||||
|         Core::Movie::GetInstance().StartPlayback(movie_playback_path.toStdString()); | ||||
|         movie_playback_on_start = false; | ||||
|         movie_playback_path.clear(); | ||||
|     } | ||||
| 
 | ||||
|     if (ui->action_Enable_Frame_Advancing->isChecked()) { | ||||
|         ui->action_Advance_Frame->setEnabled(true); | ||||
|         Core::System::GetInstance().frame_limiter.SetFrameAdvancing(true); | ||||
|     } else { | ||||
|         ui->action_Advance_Frame->setEnabled(false); | ||||
|     } | ||||
| 
 | ||||
|     if (video_dumping_on_start) { | ||||
|         Layout::FramebufferLayout layout{ | ||||
|             Layout::FrameLayoutFromResolutionScale(VideoCore::GetResolutionScaleFactor())}; | ||||
|         if (!Core::System::GetInstance().VideoDumper().StartDumping( | ||||
|                 video_dumping_path.toStdString(), layout)) { | ||||
| 
 | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui->action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|         video_dumping_on_start = false; | ||||
|         video_dumping_path.clear(); | ||||
|     } | ||||
| 
 | ||||
|     // Create and start the emulation thread
 | ||||
|     emu_thread = std::make_unique<EmuThread>(*render_window); | ||||
|     emit EmulationStarting(emu_thread.get()); | ||||
|  | @ -1055,6 +1102,8 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 
 | ||||
|     connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen, | ||||
|             &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); | ||||
|     connect(emu_thread.get(), &EmuThread::HideLoadingScreen, loading_screen, | ||||
|             &LoadingScreen::OnLoadComplete); | ||||
| 
 | ||||
|     // Update the GUI
 | ||||
|     registersWidget->OnDebugModeEntered(); | ||||
|  | @ -1062,7 +1111,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|         game_list->hide(); | ||||
|         game_list_placeholder->hide(); | ||||
|     } | ||||
|     status_bar_update_timer.start(2000); | ||||
|     status_bar_update_timer.start(1000); | ||||
| 
 | ||||
|     if (UISettings::values.hide_mouse) { | ||||
|         mouse_hide_timer.start(); | ||||
|  | @ -1081,20 +1130,6 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|         ShowFullscreen(); | ||||
|     } | ||||
| 
 | ||||
|     if (video_dumping_on_start) { | ||||
|         Layout::FramebufferLayout layout{ | ||||
|             Layout::FrameLayoutFromResolutionScale(VideoCore::GetResolutionScaleFactor())}; | ||||
|         if (!Core::System::GetInstance().VideoDumper().StartDumping( | ||||
|                 video_dumping_path.toStdString(), layout)) { | ||||
| 
 | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui->action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|         video_dumping_on_start = false; | ||||
|         video_dumping_path.clear(); | ||||
|     } | ||||
|     OnStartGame(); | ||||
| } | ||||
| 
 | ||||
|  | @ -1118,7 +1153,6 @@ void GMainWindow::ShutdownGame() { | |||
|     AllowOSSleep(); | ||||
| 
 | ||||
|     discord_rpc->Pause(); | ||||
|     OnStopRecordingPlayback(); | ||||
|     emu_thread->RequestStop(); | ||||
| 
 | ||||
|     // Release emu threads from any breakpoints
 | ||||
|  | @ -1137,6 +1171,8 @@ void GMainWindow::ShutdownGame() { | |||
|     emu_thread->wait(); | ||||
|     emu_thread = nullptr; | ||||
| 
 | ||||
|     OnCloseMovie(); | ||||
| 
 | ||||
|     discord_rpc->Update(); | ||||
| 
 | ||||
|     Camera::QtMultimediaCameraHandler::ReleaseHandlers(); | ||||
|  | @ -1154,8 +1190,6 @@ void GMainWindow::ShutdownGame() { | |||
|     ui->action_Load_Amiibo->setEnabled(false); | ||||
|     ui->action_Remove_Amiibo->setEnabled(false); | ||||
|     ui->action_Report_Compatibility->setEnabled(false); | ||||
|     ui->action_Enable_Frame_Advancing->setEnabled(false); | ||||
|     ui->action_Enable_Frame_Advancing->setChecked(false); | ||||
|     ui->action_Advance_Frame->setEnabled(false); | ||||
|     ui->action_Capture_Screenshot->setEnabled(false); | ||||
|     render_window->hide(); | ||||
|  | @ -1172,6 +1206,7 @@ void GMainWindow::ShutdownGame() { | |||
|     // Disable status bar updates
 | ||||
|     status_bar_update_timer.stop(); | ||||
|     message_label->setVisible(false); | ||||
|     message_label_used_for_movie = false; | ||||
|     emu_speed_label->setVisible(false); | ||||
|     game_fps_label->setVisible(false); | ||||
|     emu_frametime_label->setVisible(false); | ||||
|  | @ -1545,12 +1580,6 @@ void GMainWindow::OnMenuRecentFile() { | |||
| void GMainWindow::OnStartGame() { | ||||
|     Camera::QtMultimediaCameraHandler::ResumeCameras(); | ||||
| 
 | ||||
|     if (movie_record_on_start) { | ||||
|         Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString()); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|     } | ||||
| 
 | ||||
|     PreventOSSleep(); | ||||
| 
 | ||||
|     emu_thread->SetRunning(true); | ||||
|  | @ -1567,7 +1596,6 @@ void GMainWindow::OnStartGame() { | |||
|     ui->action_Cheats->setEnabled(true); | ||||
|     ui->action_Load_Amiibo->setEnabled(true); | ||||
|     ui->action_Report_Compatibility->setEnabled(true); | ||||
|     ui->action_Enable_Frame_Advancing->setEnabled(true); | ||||
|     ui->action_Capture_Screenshot->setEnabled(true); | ||||
| 
 | ||||
|     discord_rpc->Update(); | ||||
|  | @ -1851,144 +1879,81 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { | |||
| } | ||||
| 
 | ||||
| void GMainWindow::OnRecordMovie() { | ||||
|     if (emulation_running) { | ||||
|         QMessageBox::StandardButton answer = QMessageBox::warning( | ||||
|             this, tr("Record Movie"), | ||||
|             tr("To keep consistency with the RNG, it is recommended to record the movie from game " | ||||
|                "start.<br>Are you sure you still want to record movies now?"), | ||||
|             QMessageBox::Yes | QMessageBox::No); | ||||
|         if (answer == QMessageBox::No) | ||||
|             return; | ||||
|     } | ||||
|     const QString path = | ||||
|         QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|     MovieRecordDialog dialog(this); | ||||
|     if (dialog.exec() != QDialog::Accepted) { | ||||
|         return; | ||||
|     UISettings::values.movie_record_path = QFileInfo(path).path(); | ||||
|     if (emulation_running) { | ||||
|         Core::Movie::GetInstance().StartRecording(path.toStdString()); | ||||
|     } else { | ||||
|         movie_record_on_start = true; | ||||
|         movie_record_path = path; | ||||
|         QMessageBox::information(this, tr("Record Movie"), | ||||
|                                  tr("Recording will start once you boot a game.")); | ||||
|     } | ||||
|     ui->action_Record_Movie->setEnabled(false); | ||||
|     ui->action_Play_Movie->setEnabled(false); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::ValidateMovie(const QString& path, u64 program_id) { | ||||
|     using namespace Core; | ||||
|     Movie::ValidationResult result = | ||||
|         Core::Movie::GetInstance().ValidateMovie(path.toStdString(), program_id); | ||||
|     const QString revision_dismatch_text = | ||||
|         tr("The movie file you are trying to load was created on a different revision of Citra." | ||||
|            "<br/>Citra has had some changes during the time, and the playback may desync or not " | ||||
|            "work as expected." | ||||
|            "<br/><br/>Are you sure you still want to load the movie file?"); | ||||
|     const QString game_dismatch_text = | ||||
|         tr("The movie file you are trying to load was recorded with a different game." | ||||
|            "<br/>The playback may not work as expected, and it may cause unexpected results." | ||||
|            "<br/><br/>Are you sure you still want to load the movie file?"); | ||||
|     const QString invalid_movie_text = | ||||
|         tr("The movie file you are trying to load is invalid." | ||||
|            "<br/>Either the file is corrupted, or Citra has had made some major changes to the " | ||||
|            "Movie module." | ||||
|            "<br/>Please choose a different movie file and try again."); | ||||
|     int answer; | ||||
|     switch (result) { | ||||
|     case Movie::ValidationResult::RevisionDismatch: | ||||
|         answer = QMessageBox::question(this, tr("Revision Dismatch"), revision_dismatch_text, | ||||
|                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
|         if (answer != QMessageBox::Yes) | ||||
|             return false; | ||||
|         break; | ||||
|     case Movie::ValidationResult::GameDismatch: | ||||
|         answer = QMessageBox::question(this, tr("Game Dismatch"), game_dismatch_text, | ||||
|                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
|         if (answer != QMessageBox::Yes) | ||||
|             return false; | ||||
|         break; | ||||
|     case Movie::ValidationResult::Invalid: | ||||
|         QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|         return false; | ||||
|     default: | ||||
|         break; | ||||
|     movie_record_on_start = true; | ||||
|     movie_record_path = dialog.GetPath(); | ||||
|     movie_record_author = dialog.GetAuthor(); | ||||
| 
 | ||||
|     if (emulation_running) { // Restart game
 | ||||
|         BootGame(QString(game_path)); | ||||
|     } | ||||
|     return true; | ||||
|     ui->action_Close_Movie->setEnabled(true); | ||||
|     ui->action_Save_Movie->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnPlayMovie() { | ||||
|     if (emulation_running) { | ||||
|         QMessageBox::StandardButton answer = QMessageBox::warning( | ||||
|             this, tr("Play Movie"), | ||||
|             tr("To keep consistency with the RNG, it is recommended to play the movie from game " | ||||
|                "start.<br>Are you sure you still want to play movies now?"), | ||||
|             QMessageBox::Yes | QMessageBox::No); | ||||
|         if (answer == QMessageBox::No) | ||||
|             return; | ||||
|     } | ||||
| 
 | ||||
|     const QString path = | ||||
|         QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|     MoviePlayDialog dialog(this, game_list); | ||||
|     if (dialog.exec() != QDialog::Accepted) { | ||||
|         return; | ||||
|     UISettings::values.movie_playback_path = QFileInfo(path).path(); | ||||
| 
 | ||||
|     if (emulation_running) { | ||||
|         if (!ValidateMovie(path)) | ||||
|             return; | ||||
|     } else { | ||||
|         const QString invalid_movie_text = | ||||
|             tr("The movie file you are trying to load is invalid." | ||||
|                "<br/>Either the file is corrupted, or Citra has had made some major changes to the " | ||||
|                "Movie module." | ||||
|                "<br/>Please choose a different movie file and try again."); | ||||
|         u64 program_id = Core::Movie::GetInstance().GetMovieProgramID(path.toStdString()); | ||||
|         if (!program_id) { | ||||
|             QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|             return; | ||||
|         } | ||||
|         QString game_path = game_list->FindGameByProgramID(program_id); | ||||
|         if (game_path.isEmpty()) { | ||||
|             QMessageBox::warning(this, tr("Game Not Found"), | ||||
|                                  tr("The movie you are trying to play is from a game that is not " | ||||
|                                     "in the game list. If you own the game, please add the game " | ||||
|                                     "folder to the game list and try to play the movie again.")); | ||||
|             return; | ||||
|         } | ||||
|         if (!ValidateMovie(path, program_id)) | ||||
|             return; | ||||
|         Core::Movie::GetInstance().PrepareForPlayback(path.toStdString()); | ||||
|         BootGame(game_path); | ||||
|     } | ||||
|     Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] { | ||||
|         QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); | ||||
|     }); | ||||
|     ui->action_Record_Movie->setEnabled(false); | ||||
|     ui->action_Play_Movie->setEnabled(false); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(true); | ||||
| 
 | ||||
|     movie_playback_on_start = true; | ||||
|     movie_playback_path = dialog.GetMoviePath(); | ||||
|     BootGame(dialog.GetGamePath()); | ||||
| 
 | ||||
|     ui->action_Close_Movie->setEnabled(true); | ||||
|     ui->action_Save_Movie->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnStopRecordingPlayback() { | ||||
| void GMainWindow::OnCloseMovie() { | ||||
|     if (movie_record_on_start) { | ||||
|         QMessageBox::information(this, tr("Record Movie"), tr("Movie recording cancelled.")); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|         movie_record_author.clear(); | ||||
|     } else { | ||||
|         const bool was_recording = Core::Movie::GetInstance().IsRecordingInput(); | ||||
|         const bool was_running = emu_thread && emu_thread->IsRunning(); | ||||
|         if (was_running) { | ||||
|             OnPauseGame(); | ||||
|         } | ||||
| 
 | ||||
|         const bool was_recording = | ||||
|             Core::Movie::GetInstance().GetPlayMode() == Core::Movie::PlayMode::Recording; | ||||
|         Core::Movie::GetInstance().Shutdown(); | ||||
|         if (was_recording) { | ||||
|             QMessageBox::information(this, tr("Movie Saved"), | ||||
|                                      tr("The movie is successfully saved.")); | ||||
|         } | ||||
| 
 | ||||
|         if (was_running) { | ||||
|             OnStartGame(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ui->action_Close_Movie->setEnabled(false); | ||||
|     ui->action_Save_Movie->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnSaveMovie() { | ||||
|     const bool was_running = emu_thread && emu_thread->IsRunning(); | ||||
|     if (was_running) { | ||||
|         OnPauseGame(); | ||||
|     } | ||||
| 
 | ||||
|     if (Core::Movie::GetInstance().GetPlayMode() == Core::Movie::PlayMode::Recording) { | ||||
|         Core::Movie::GetInstance().SaveMovie(); | ||||
|         QMessageBox::information(this, tr("Movie Saved"), tr("The movie is successfully saved.")); | ||||
|     } else { | ||||
|         LOG_ERROR(Frontend, "Tried to save movie while movie is not being recorded"); | ||||
|     } | ||||
| 
 | ||||
|     if (was_running) { | ||||
|         OnStartGame(); | ||||
|     } | ||||
|     ui->action_Record_Movie->setEnabled(true); | ||||
|     ui->action_Play_Movie->setEnabled(true); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnCaptureScreenshot() { | ||||
|  | @ -2067,6 +2032,32 @@ void GMainWindow::UpdateStatusBar() { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Update movie status
 | ||||
|     const u64 current = Core::Movie::GetInstance().GetCurrentInputIndex(); | ||||
|     const u64 total = Core::Movie::GetInstance().GetTotalInputCount(); | ||||
|     const auto play_mode = Core::Movie::GetInstance().GetPlayMode(); | ||||
|     if (play_mode == Core::Movie::PlayMode::Recording) { | ||||
|         message_label->setText(tr("Recording %1").arg(current)); | ||||
|         message_label->setVisible(true); | ||||
|         message_label_used_for_movie = true; | ||||
|         ui->action_Save_Movie->setEnabled(true); | ||||
|     } else if (play_mode == Core::Movie::PlayMode::Playing) { | ||||
|         message_label->setText(tr("Playing %1 / %2").arg(current).arg(total)); | ||||
|         message_label->setVisible(true); | ||||
|         message_label_used_for_movie = true; | ||||
|         ui->action_Save_Movie->setEnabled(false); | ||||
|     } else if (play_mode == Core::Movie::PlayMode::MovieFinished) { | ||||
|         message_label->setText(tr("Movie Finished")); | ||||
|         message_label->setVisible(true); | ||||
|         message_label_used_for_movie = true; | ||||
|         ui->action_Save_Movie->setEnabled(false); | ||||
|     } else if (message_label_used_for_movie) { // Clear the label if movie was just closed
 | ||||
|         message_label->setText(QString{}); | ||||
|         message_label->setVisible(false); | ||||
|         message_label_used_for_movie = false; | ||||
|         ui->action_Save_Movie->setEnabled(false); | ||||
|     } | ||||
| 
 | ||||
|     auto results = Core::System::GetInstance().GetAndResetPerfStats(); | ||||
| 
 | ||||
|     if (Settings::values.use_frame_limit_alternate) { | ||||
|  | @ -2178,6 +2169,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det | |||
|             emu_thread->SetRunning(true); | ||||
|             message_label->setText(status_message); | ||||
|             message_label->setVisible(true); | ||||
|             message_label_used_for_movie = false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -2356,10 +2348,8 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { | |||
| } | ||||
| 
 | ||||
| void GMainWindow::OnMoviePlaybackCompleted() { | ||||
|     OnPauseGame(); | ||||
|     QMessageBox::information(this, tr("Playback Completed"), tr("Movie playback completed.")); | ||||
|     ui->action_Record_Movie->setEnabled(true); | ||||
|     ui->action_Play_Movie->setEnabled(true); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::UpdateWindowTitle() { | ||||
|  |  | |||
|  | @ -208,7 +208,8 @@ private slots: | |||
|     void OnCreateGraphicsSurfaceViewer(); | ||||
|     void OnRecordMovie(); | ||||
|     void OnPlayMovie(); | ||||
|     void OnStopRecordingPlayback(); | ||||
|     void OnCloseMovie(); | ||||
|     void OnSaveMovie(); | ||||
|     void OnCaptureScreenshot(); | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     void OnStartVideoDumping(); | ||||
|  | @ -224,7 +225,6 @@ private slots: | |||
|     void OnMouseActivity(); | ||||
| 
 | ||||
| private: | ||||
|     bool ValidateMovie(const QString& path, u64 program_id = 0); | ||||
|     Q_INVOKABLE void OnMoviePlaybackCompleted(); | ||||
|     void UpdateStatusBar(); | ||||
|     void LoadTranslation(); | ||||
|  | @ -249,6 +249,7 @@ private: | |||
|     QLabel* game_fps_label = nullptr; | ||||
|     QLabel* emu_frametime_label = nullptr; | ||||
|     QTimer status_bar_update_timer; | ||||
|     bool message_label_used_for_movie = false; | ||||
| 
 | ||||
|     MultiplayerState* multiplayer_state = nullptr; | ||||
|     std::unique_ptr<Config> config; | ||||
|  | @ -267,6 +268,10 @@ private: | |||
|     // Movie
 | ||||
|     bool movie_record_on_start = false; | ||||
|     QString movie_record_path; | ||||
|     QString movie_record_author; | ||||
| 
 | ||||
|     bool movie_playback_on_start = false; | ||||
|     QString movie_playback_path; | ||||
| 
 | ||||
|     // Video dumping
 | ||||
|     bool video_dumping_on_start = false; | ||||
|  |  | |||
|  | @ -163,7 +163,10 @@ | |||
|      </property> | ||||
|      <addaction name="action_Record_Movie"/> | ||||
|      <addaction name="action_Play_Movie"/> | ||||
|      <addaction name="action_Stop_Recording_Playback"/> | ||||
|      <addaction name="action_Close_Movie"/> | ||||
|      <addaction name="separator"/> | ||||
|      <addaction name="action_Movie_Read_Only_Mode"/> | ||||
|      <addaction name="action_Save_Movie"/> | ||||
|     </widget> | ||||
|     <widget class="QMenu" name="menu_Frame_Advance"> | ||||
|      <property name="title"> | ||||
|  | @ -318,36 +321,43 @@ | |||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Record_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Record Movie</string> | ||||
|     <string>Record...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Play_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Play Movie</string> | ||||
|     <string>Play...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Stop_Recording_Playback"> | ||||
|   <action name="action_Close_Movie"> | ||||
|    <property name="text"> | ||||
|     <string>Close</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Save_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Stop Recording / Playback</string> | ||||
|     <string>Save without Closing</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Movie_Read_Only_Mode"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="checked"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Read-Only Mode</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Enable_Frame_Advancing"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Enable Frame Advancing</string> | ||||
|    </property> | ||||
|  |  | |||
							
								
								
									
										130
									
								
								src/citra_qt/movie/movie_play_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/citra_qt/movie/movie_play_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,130 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QFileDialog> | ||||
| #include <QPushButton> | ||||
| #include <QTime> | ||||
| #include "citra_qt/game_list.h" | ||||
| #include "citra_qt/game_list_p.h" | ||||
| #include "citra_qt/movie/movie_play_dialog.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/service/hid/hid.h" | ||||
| #include "core/movie.h" | ||||
| #include "ui_movie_play_dialog.h" | ||||
| 
 | ||||
| MoviePlayDialog::MoviePlayDialog(QWidget* parent, GameList* game_list_) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::MoviePlayDialog>()), game_list(game_list_) { | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
| 
 | ||||
|     connect(ui->filePathButton, &QToolButton::clicked, this, &MoviePlayDialog::OnToolButtonClicked); | ||||
|     connect(ui->filePath, &QLineEdit::editingFinished, this, &MoviePlayDialog::UpdateUIDisplay); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &MoviePlayDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MoviePlayDialog::reject); | ||||
| 
 | ||||
|     if (Core::System::GetInstance().IsPoweredOn()) { | ||||
|         QString note_text; | ||||
|         note_text = tr("Current running game will be stopped."); | ||||
|         if (Core::Movie::GetInstance().GetPlayMode() == Core::Movie::PlayMode::Recording) { | ||||
|             note_text.append(tr("<br>Current recording will be discarded.")); | ||||
|         } | ||||
|         ui->note2Label->setText(note_text); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MoviePlayDialog::~MoviePlayDialog() = default; | ||||
| 
 | ||||
| QString MoviePlayDialog::GetMoviePath() const { | ||||
|     return ui->filePath->text(); | ||||
| } | ||||
| 
 | ||||
| QString MoviePlayDialog::GetGamePath() const { | ||||
|     const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(GetMoviePath().toStdString()); | ||||
|     return game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::FullPathRole); | ||||
| } | ||||
| 
 | ||||
| void MoviePlayDialog::OnToolButtonClicked() { | ||||
|     const QString path = | ||||
|         QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|     ui->filePath->setText(path); | ||||
|     UISettings::values.movie_playback_path = path; | ||||
|     UpdateUIDisplay(); | ||||
| } | ||||
| 
 | ||||
| void MoviePlayDialog::UpdateUIDisplay() { | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); | ||||
|     ui->gameLineEdit->clear(); | ||||
|     ui->authorLineEdit->clear(); | ||||
|     ui->rerecordCountLineEdit->clear(); | ||||
|     ui->lengthLineEdit->clear(); | ||||
|     ui->note1Label->setVisible(true); | ||||
| 
 | ||||
|     const auto path = GetMoviePath().toStdString(); | ||||
| 
 | ||||
|     const auto validation_result = Core::Movie::GetInstance().ValidateMovie(path); | ||||
|     if (validation_result == Core::Movie::ValidationResult::Invalid) { | ||||
|         ui->note1Label->setText(tr("Invalid movie file.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ui->note2Label->setVisible(true); | ||||
|     ui->infoGroupBox->setVisible(true); | ||||
| 
 | ||||
|     switch (validation_result) { | ||||
|     case Core::Movie::ValidationResult::OK: | ||||
|         ui->note1Label->setText(QString{}); | ||||
|         break; | ||||
|     case Core::Movie::ValidationResult::RevisionDismatch: | ||||
|         ui->note1Label->setText(tr("Revision dismatch, playback may desync.")); | ||||
|         break; | ||||
|     case Core::Movie::ValidationResult::InputCountDismatch: | ||||
|         ui->note1Label->setText(tr("Indicated length is incorrect, file may be corrupted.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|         break; | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| 
 | ||||
|     const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(path); | ||||
| 
 | ||||
|     // Format game title
 | ||||
|     const auto title = | ||||
|         game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::TitleRole); | ||||
|     if (title.isEmpty()) { | ||||
|         ui->gameLineEdit->setText(tr("(unknown)")); | ||||
|         ui->note1Label->setText(tr("Game used in this movie is not in game list.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|     } else { | ||||
|         ui->gameLineEdit->setText(title); | ||||
|     } | ||||
| 
 | ||||
|     ui->authorLineEdit->setText(metadata.author.empty() ? tr("(unknown)") | ||||
|                                                         : QString::fromStdString(metadata.author)); | ||||
|     ui->rerecordCountLineEdit->setText( | ||||
|         metadata.rerecord_count == 0 ? tr("(unknown)") : QString::number(metadata.rerecord_count)); | ||||
| 
 | ||||
|     // Format length
 | ||||
|     if (metadata.input_count == 0) { | ||||
|         ui->lengthLineEdit->setText(tr("(unknown)")); | ||||
|     } else { | ||||
|         if (metadata.input_count > | ||||
|             BASE_CLOCK_RATE_ARM11 * 24 * 60 * 60 / Service::HID::Module::pad_update_ticks) { | ||||
|             // More than a day
 | ||||
|             ui->lengthLineEdit->setText(tr("(>1 day)")); | ||||
|         } else { | ||||
|             const u64 msecs = Service::HID::Module::pad_update_ticks * metadata.input_count * 1000 / | ||||
|                               BASE_CLOCK_RATE_ARM11; | ||||
|             ui->lengthLineEdit->setText( | ||||
|                 QTime::fromMSecsSinceStartOfDay(msecs).toString(QStringLiteral("hh:mm:ss.zzz"))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/citra_qt/movie/movie_play_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/citra_qt/movie/movie_play_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| 
 | ||||
| class GameList; | ||||
| 
 | ||||
| namespace Ui { | ||||
| class MoviePlayDialog; | ||||
| } | ||||
| 
 | ||||
| class MoviePlayDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit MoviePlayDialog(QWidget* parent, GameList* game_list); | ||||
|     ~MoviePlayDialog() override; | ||||
| 
 | ||||
|     QString GetMoviePath() const; | ||||
|     QString GetGamePath() const; | ||||
| 
 | ||||
| private: | ||||
|     void OnToolButtonClicked(); | ||||
|     void UpdateUIDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::MoviePlayDialog> ui; | ||||
|     GameList* game_list; | ||||
| }; | ||||
							
								
								
									
										136
									
								
								src/citra_qt/movie/movie_play_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/citra_qt/movie/movie_play_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,136 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>MoviePlayDialog</class> | ||||
|  <widget class="QDialog" name="MoviePlayDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>100</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Play Movie</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>File:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLineEdit" name="filePath"/> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QToolButton" name="filePathButton"> | ||||
|        <property name="text"> | ||||
|         <string>...</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="note1Label"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="infoGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Info</string> | ||||
|      </property> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|      <layout class="QFormLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Game:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QLineEdit" name="gameLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Author:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="authorLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel" name="rerecordCountLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Rerecord Count:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QLineEdit" name="rerecordCountLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="0"> | ||||
|        <widget class="QLabel" name="lengthLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Length:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="1"> | ||||
|        <widget class="QLineEdit" name="lengthLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="note2Label"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
							
								
								
									
										61
									
								
								src/citra_qt/movie/movie_record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/citra_qt/movie/movie_record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QFileDialog> | ||||
| #include <QPushButton> | ||||
| #include "citra_qt/movie/movie_record_dialog.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/movie.h" | ||||
| #include "ui_movie_record_dialog.h" | ||||
| 
 | ||||
| MovieRecordDialog::MovieRecordDialog(QWidget* parent) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::MovieRecordDialog>()) { | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
| 
 | ||||
|     connect(ui->filePathButton, &QToolButton::clicked, this, | ||||
|             &MovieRecordDialog::OnToolButtonClicked); | ||||
|     connect(ui->filePath, &QLineEdit::editingFinished, this, &MovieRecordDialog::UpdateUIDisplay); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &MovieRecordDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MovieRecordDialog::reject); | ||||
| 
 | ||||
|     QString note_text; | ||||
|     if (Core::System::GetInstance().IsPoweredOn()) { | ||||
|         note_text = tr("Current running game will be restarted."); | ||||
|         if (Core::Movie::GetInstance().GetPlayMode() == Core::Movie::PlayMode::Recording) { | ||||
|             note_text.append(tr("<br>Current recording will be discarded.")); | ||||
|         } | ||||
|     } else { | ||||
|         note_text = tr("Recording will start once you boot a game."); | ||||
|     } | ||||
|     ui->noteLabel->setText(note_text); | ||||
| } | ||||
| 
 | ||||
| MovieRecordDialog::~MovieRecordDialog() = default; | ||||
| 
 | ||||
| QString MovieRecordDialog::GetPath() const { | ||||
|     return ui->filePath->text(); | ||||
| } | ||||
| 
 | ||||
| QString MovieRecordDialog::GetAuthor() const { | ||||
|     return ui->authorLineEdit->text(); | ||||
| } | ||||
| 
 | ||||
| void MovieRecordDialog::OnToolButtonClicked() { | ||||
|     const QString path = | ||||
|         QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|     ui->filePath->setText(path); | ||||
|     UISettings::values.movie_record_path = path; | ||||
|     UpdateUIDisplay(); | ||||
| } | ||||
| 
 | ||||
| void MovieRecordDialog::UpdateUIDisplay() { | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!ui->filePath->text().isEmpty()); | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/citra_qt/movie/movie_record_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/citra_qt/movie/movie_record_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| 
 | ||||
| namespace Ui { | ||||
| class MovieRecordDialog; | ||||
| } | ||||
| 
 | ||||
| class MovieRecordDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit MovieRecordDialog(QWidget* parent); | ||||
|     ~MovieRecordDialog() override; | ||||
| 
 | ||||
|     QString GetPath() const; | ||||
|     QString GetAuthor() const; | ||||
| 
 | ||||
| private: | ||||
|     void OnToolButtonClicked(); | ||||
|     void UpdateUIDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::MovieRecordDialog> ui; | ||||
| }; | ||||
							
								
								
									
										71
									
								
								src/citra_qt/movie/movie_record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/citra_qt/movie/movie_record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>MovieRecordDialog</class> | ||||
|  <widget class="QDialog" name="MovieRecordDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>150</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Record Movie</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QGridLayout"> | ||||
|      <item row="0" column="0"> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>File:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="0" column="1"> | ||||
|       <widget class="QLineEdit" name="filePath"/> | ||||
|      </item> | ||||
|      <item row="0" column="2"> | ||||
|       <widget class="QToolButton" name="filePathButton"> | ||||
|        <property name="text"> | ||||
|         <string>...</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="1" column="0"> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>Author:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="1" column="1"> | ||||
|       <widget class="QLineEdit" name="authorLineEdit"> | ||||
|        <property name="maxLength"> | ||||
|         <number>32</number> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="noteLabel"/> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue