From 113e0c7331780ad2e975bf43c9b9c1848f67b9e5 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 6 Jul 2020 21:44:17 +0800 Subject: [PATCH] citra_qt: Rebuilt movie frontend This is completely rebuilt, in order to allow setting author, displaying movie metadata, and toggling read-only mode. The UX is changed to more closely match other emulators' behaviour. Now you can only record/play from start/reset (In the future, we might want to introduce 'record from savestate') Also fixed a critical bug where movie file can be corrupted when ending the recording while game is still running. --- src/citra_qt/CMakeLists.txt | 6 + src/citra_qt/game_list.cpp | 10 +- src/citra_qt/game_list.h | 4 +- src/citra_qt/main.cpp | 167 ++++++--------------- src/citra_qt/main.h | 4 +- src/citra_qt/main.ui | 34 +++-- src/citra_qt/movie/movie_play_dialog.cpp | 130 ++++++++++++++++ src/citra_qt/movie/movie_play_dialog.h | 30 ++++ src/citra_qt/movie/movie_play_dialog.ui | 136 +++++++++++++++++ src/citra_qt/movie/movie_record_dialog.cpp | 61 ++++++++ src/citra_qt/movie/movie_record_dialog.h | 27 ++++ src/citra_qt/movie/movie_record_dialog.ui | 71 +++++++++ src/core/hle/service/hid/hid.cpp | 6 - src/core/hle/service/hid/hid.h | 6 + 14 files changed, 541 insertions(+), 151 deletions(-) create mode 100644 src/citra_qt/movie/movie_play_dialog.cpp create mode 100644 src/citra_qt/movie/movie_play_dialog.h create mode 100644 src/citra_qt/movie/movie_play_dialog.ui create mode 100644 src/citra_qt/movie/movie_record_dialog.cpp create mode 100644 src/citra_qt/movie/movie_record_dialog.h create mode 100644 src/citra_qt/movie/movie_record_dialog.ui diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 24c2ca601..320e727a1 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -125,6 +125,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 diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index d75df2859..613839807 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -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(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; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 8383f9aaf..e76c0edee 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -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; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index c8639f616..b3242bcef 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -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,9 @@ GMainWindow::GMainWindow() Network::Init(); + Core::Movie::GetInstance().SetPlaybackCompletionCallback( + [this] { QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); }); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -742,8 +747,9 @@ 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_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( @@ -1105,7 +1111,7 @@ void GMainWindow::ShutdownGame() { AllowOSSleep(); discord_rpc->Pause(); - OnStopRecordingPlayback(); + OnCloseMovie(true); emu_thread->RequestStop(); // Release emu threads from any breakpoints @@ -1534,9 +1540,11 @@ void GMainWindow::OnStartGame() { Camera::QtMultimediaCameraHandler::ResumeCameras(); if (movie_record_on_start) { - Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString()); + 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(); } PreventOSSleep(); @@ -1839,144 +1847,63 @@ 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.
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()); + // Restart game + BootGame(QString(game_path)); + Core::Movie::GetInstance().StartRecording(dialog.GetPath().toStdString(), + dialog.GetAuthor().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.")); + movie_record_path = dialog.GetPath(); + movie_record_author = dialog.GetAuthor(); } - 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." - "
Citra has had some changes during the time, and the playback may desync or not " - "work as expected." - "

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." - "
The playback may not work as expected, and it may cause unexpected results." - "

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." - "
Either the file is corrupted, or Citra has had made some major changes to the " - "Movie module." - "
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; - } - return true; + ui->action_Close_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.
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." - "
Either the file is corrupted, or Citra has had made some major changes to the " - "Movie module." - "
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); + + const auto movie_path = dialog.GetMoviePath().toStdString(); + Core::Movie::GetInstance().PrepareForPlayback(movie_path); + BootGame(dialog.GetGamePath()); + + Core::Movie::GetInstance().StartPlayback(movie_path); + ui->action_Close_Movie->setEnabled(true); } -void GMainWindow::OnStopRecordingPlayback() { +void GMainWindow::OnCloseMovie(bool shutting_down) { 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_running = !shutting_down && emu_thread && emu_thread->IsRunning(); + if (was_running) { + OnPauseGame(); + } + const bool was_recording = Core::Movie::GetInstance().IsRecordingInput(); 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_Record_Movie->setEnabled(true); - ui->action_Play_Movie->setEnabled(true); - ui->action_Stop_Recording_Playback->setEnabled(false); + + ui->action_Close_Movie->setEnabled(false); } void GMainWindow::OnCaptureScreenshot() { @@ -2345,9 +2272,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { void GMainWindow::OnMoviePlaybackCompleted() { 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); + ui->action_Close_Movie->setEnabled(false); } void GMainWindow::UpdateWindowTitle() { diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 241365a38..eff5ea7b5 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -208,7 +208,7 @@ private slots: void OnCreateGraphicsSurfaceViewer(); void OnRecordMovie(); void OnPlayMovie(); - void OnStopRecordingPlayback(); + void OnCloseMovie(bool shutting_down = false); void OnCaptureScreenshot(); #ifdef ENABLE_FFMPEG_VIDEO_DUMPER void OnStartVideoDumping(); @@ -224,7 +224,6 @@ private slots: void OnMouseActivity(); private: - bool ValidateMovie(const QString& path, u64 program_id = 0); Q_INVOKABLE void OnMoviePlaybackCompleted(); void UpdateStatusBar(); void LoadTranslation(); @@ -267,6 +266,7 @@ private: // Movie bool movie_record_on_start = false; QString movie_record_path; + QString movie_record_author; // Video dumping bool video_dumping_on_start = false; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 2eff98083..5d2d4f0ca 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -163,7 +163,9 @@ - + + + @@ -318,27 +320,29 @@ - - true - - Record Movie + Record... - + + Play... + + + + + Close + + + + + true + + true - Play Movie - - - - - false - - - Stop Recording / Playback + Read-Only Mode diff --git a/src/citra_qt/movie/movie_play_dialog.cpp b/src/citra_qt/movie/movie_play_dialog.cpp new file mode 100644 index 000000000..bfb38341a --- /dev/null +++ b/src/citra_qt/movie/movie_play_dialog.cpp @@ -0,0 +1,130 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#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()), 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().IsRecordingInput()) { + note_text.append(tr("
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"))); + } + } +} diff --git a/src/citra_qt/movie/movie_play_dialog.h b/src/citra_qt/movie/movie_play_dialog.h new file mode 100644 index 000000000..dc4f344a5 --- /dev/null +++ b/src/citra_qt/movie/movie_play_dialog.h @@ -0,0 +1,30 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +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; + GameList* game_list; +}; diff --git a/src/citra_qt/movie/movie_play_dialog.ui b/src/citra_qt/movie/movie_play_dialog.ui new file mode 100644 index 000000000..ad9b595cd --- /dev/null +++ b/src/citra_qt/movie/movie_play_dialog.ui @@ -0,0 +1,136 @@ + + + MoviePlayDialog + + + + 0 + 0 + 600 + 100 + + + + Play Movie + + + + + + + + File: + + + + + + + + + + ... + + + + + + + + + false + + + + + + + Info + + + false + + + + + + Game: + + + + + + + true + + + + + + + Author: + + + + + + + true + + + + + + + Rerecord Count: + + + + + + + true + + + + + + + Length: + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + + + + false + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + diff --git a/src/citra_qt/movie/movie_record_dialog.cpp b/src/citra_qt/movie/movie_record_dialog.cpp new file mode 100644 index 000000000..6f0954909 --- /dev/null +++ b/src/citra_qt/movie/movie_record_dialog.cpp @@ -0,0 +1,61 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#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->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().IsRecordingInput()) { + note_text.append(tr("
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()); +} diff --git a/src/citra_qt/movie/movie_record_dialog.h b/src/citra_qt/movie/movie_record_dialog.h new file mode 100644 index 000000000..c91f1f414 --- /dev/null +++ b/src/citra_qt/movie/movie_record_dialog.h @@ -0,0 +1,27 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +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; +}; diff --git a/src/citra_qt/movie/movie_record_dialog.ui b/src/citra_qt/movie/movie_record_dialog.ui new file mode 100644 index 000000000..96298b8e4 --- /dev/null +++ b/src/citra_qt/movie/movie_record_dialog.ui @@ -0,0 +1,71 @@ + + + MovieRecordDialog + + + + 0 + 0 + 600 + 150 + + + + Record Movie + + + + + + + + File: + + + + + + + + + + ... + + + + + + + Author: + + + + + + + 32 + + + + + + + + + Qt::Vertical + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index c3034b824..c59826551 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -12,7 +12,6 @@ #include "common/logging/log.h" #include "core/3ds.h" #include "core/core.h" -#include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" @@ -55,11 +54,6 @@ void Module::serialize(Archive& ar, const unsigned int file_version) { } SERIALIZE_IMPL(Module) -// Updating period for each HID device. These empirical values are measured from a 11.2 3DS. -constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; -constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; -constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; - constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index bdd106018..b364c4be8 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -13,6 +13,7 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "core/core_timing.h" #include "core/frontend/input.h" #include "core/hle/service/service.h" #include "core/settings.h" @@ -299,6 +300,11 @@ public: const PadState& GetState() const; + // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. + static constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; + static constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; + static constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; + private: void LoadInputDevices(); void UpdatePadCallback(u64 userdata, s64 cycles_late);