diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/generalsettingswidget.cpp index c37f2fe5b..4e206e508 100644 --- a/src/duckstation-qt/generalsettingswidget.cpp +++ b/src/duckstation-qt/generalsettingswidget.cpp @@ -9,6 +9,7 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "Main/StartPaused"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startFullscreen, "Main/StartFullscreen"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.renderToMain, "Main/RenderToMainWindow"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "Main/SaveStateOnExit"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main/ConfirmPowerOff"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display/ShowOSDMessages"); @@ -29,16 +30,19 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW onEnableSpeedLimiterStateChanged(); onEmulationSpeedValueChanged(m_ui.emulationSpeed->value()); - dialog->registerWidgetHelp(m_ui.pauseOnStart, "Pause On Start", "Unchecked", - "Pauses the emulator when a game is started."); - dialog->registerWidgetHelp(m_ui.startFullscreen, "Start Fullscreen", "Unchecked", - "Automatically switches to fullscreen mode when a game is started."); - dialog->registerWidgetHelp(m_ui.saveStateOnExit, "Save State On Exit", "Checked", - "Automatically saves the emulator state when powering down or exiting. You can then " - "resume directly from where you left off next time."); dialog->registerWidgetHelp(m_ui.confirmPowerOff, "Confirm Power Off", "Checked", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game " "when the hotkey is pressed."); + dialog->registerWidgetHelp(m_ui.saveStateOnExit, "Save State On Exit", "Checked", + "Automatically saves the emulator state when powering down or exiting. You can then " + "resume directly from where you left off next time."); + dialog->registerWidgetHelp(m_ui.startFullscreen, "Start Fullscreen", "Unchecked", + "Automatically switches to fullscreen mode when a game is started."); + dialog->registerWidgetHelp(m_ui.renderToMain, "Render To Main Window", "Checked", + "Renders the display of the simulated console to the main window of the application, over " + "the game list. If unchecked, the display will render in a seperate window."); + dialog->registerWidgetHelp(m_ui.pauseOnStart, "Pause On Start", "Unchecked", + "Pauses the emulator when a game is started."); dialog->registerWidgetHelp(m_ui.enableSpeedLimiter, "Enable Speed Limiter", "Checked", "Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will " "run as fast as possible, which may not be playable."); diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/generalsettingswidget.ui index 30c96fabb..c4afd34c0 100644 --- a/src/duckstation-qt/generalsettingswidget.ui +++ b/src/duckstation-qt/generalsettingswidget.ui @@ -33,30 +33,37 @@ - + - Pause On Start + Confirm Power Off - - - Start Fullscreen - - - - Save State On Exit - - + + - Confirm Power Off + Start Fullscreen + + + + + + + Render To Main Window + + + + + + + Pause On Start diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index c3d023488..48580b318 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -60,18 +60,34 @@ bool MainWindow::confirmMessage(const QString& message) return (result == QMessageBox::Yes); } -void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_device) +void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_device, bool fullscreen, + bool render_to_main) { DebugAssert(!m_display_widget); m_display_widget = m_host_interface->createDisplayWidget(); + m_display_widget->setWindowTitle(windowTitle()); + m_display_widget->setWindowIcon(windowIcon()); DebugAssert(m_display_widget); m_display_widget->setFocusPolicy(Qt::StrongFocus); - m_ui.mainContainer->insertWidget(1, m_display_widget); + + if (fullscreen) + { + m_display_widget->showFullScreen(); + m_display_widget->setCursor(Qt::BlankCursor); + } + else if (!render_to_main) + { + m_display_widget->showNormal(); + } + else + { + m_ui.mainContainer->insertWidget(1, m_display_widget); + switchToEmulationView(); + } // we need the surface visible.. this might be able to be replaced with something else - switchToEmulationView(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); m_display_widget->createDeviceContext(worker_thread, use_debug_device); @@ -81,36 +97,60 @@ void MainWindow::destroyDisplayWindow() { DebugAssert(m_display_widget); - const bool was_fullscreen = m_display_widget->isFullScreen(); - if (was_fullscreen) - toggleFullscreen(); + if (m_display_widget->isFullScreen()) + m_display_widget->showNormal(); - switchToGameListView(); + if (m_display_widget->parent()) + { + m_ui.mainContainer->removeWidget(m_display_widget); + switchToGameListView(); + } // recreate the display widget using the potentially-new renderer - m_ui.mainContainer->removeWidget(m_display_widget); delete m_display_widget; m_display_widget = nullptr; } -void MainWindow::setFullscreen(bool fullscreen) +void MainWindow::updateDisplayWindow(bool fullscreen, bool render_to_main) { - if (fullscreen == m_display_widget->isFullScreen()) + const bool is_fullscreen = m_display_widget->isFullScreen(); + const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); + if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) return; - if (fullscreen) + if (fullscreen || !render_to_main) { - m_ui.mainContainer->setCurrentIndex(0); - m_ui.mainContainer->removeWidget(m_display_widget); - m_display_widget->setParent(nullptr); - m_display_widget->showFullScreen(); - m_display_widget->setCursor(Qt::BlankCursor); + if (m_display_widget->parent()) + { + m_ui.mainContainer->setCurrentIndex(0); + m_ui.mainContainer->removeWidget(m_display_widget); + m_display_widget->setParent(nullptr); + switchToGameListView(); + } + + if (fullscreen) + { + m_display_widget->showFullScreen(); + m_display_widget->setCursor(Qt::BlankCursor); + } + else + { + // if we don't position it, it ends up in the top-left corner with the title bar obscured + m_display_widget->setCursor(QCursor()); + m_display_widget->showNormal(); + m_display_widget->move(pos()); + } } else { + // render-to-main + if (!m_display_widget->parent()) + { + m_ui.mainContainer->insertWidget(1, m_display_widget); + m_ui.mainContainer->setCurrentIndex(1); + } + m_display_widget->setCursor(QCursor()); - m_ui.mainContainer->insertWidget(1, m_display_widget); - m_ui.mainContainer->setCurrentIndex(1); } m_display_widget->setFocus(); @@ -119,11 +159,6 @@ void MainWindow::setFullscreen(bool fullscreen) m_ui.actionFullscreen->setChecked(fullscreen); } -void MainWindow::toggleFullscreen() -{ - setFullscreen(!m_display_widget->isFullScreen()); -} - void MainWindow::focusDisplayWidget() { if (m_ui.mainContainer->currentIndex() != 1) @@ -176,6 +211,9 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga setWindowTitle(tr("DuckStation")); else setWindowTitle(game_title); + + if (m_display_widget) + m_display_widget->setWindowTitle(windowTitle()); } void MainWindow::onStartDiscActionTriggered() @@ -419,7 +457,8 @@ void MainWindow::switchToGameListView() void MainWindow::switchToEmulationView() { - m_ui.mainContainer->setCurrentIndex(1); + if (m_display_widget->parent()) + m_ui.mainContainer->setCurrentIndex(1); m_display_widget->setFocus(); } @@ -445,7 +484,7 @@ void MainWindow::connectSignals() connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); }); connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); }); connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close); - connect(m_ui.actionFullscreen, &QAction::triggered, this, &MainWindow::toggleFullscreen); + connect(m_ui.actionFullscreen, &QAction::triggered, m_host_interface, &QtHostInterface::toggleFullscreen); connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::Count); }); connect(m_ui.actionGeneralSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::GeneralSettings); }); @@ -472,8 +511,8 @@ void MainWindow::connectSignals() connect(m_host_interface, &QtHostInterface::createDisplayWindowRequested, this, &MainWindow::createDisplayWindow, Qt::BlockingQueuedConnection); connect(m_host_interface, &QtHostInterface::destroyDisplayWindowRequested, this, &MainWindow::destroyDisplayWindow); - connect(m_host_interface, &QtHostInterface::setFullscreenRequested, this, &MainWindow::setFullscreen); - connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen); + connect(m_host_interface, &QtHostInterface::updateDisplayWindowRequested, this, &MainWindow::updateDisplayWindow, + Qt::BlockingQueuedConnection); connect(m_host_interface, &QtHostInterface::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget); connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted); connect(m_host_interface, &QtHostInterface::emulationStopped, this, &MainWindow::onEmulationStopped); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 17960e061..cc50ab07a 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -28,10 +28,9 @@ private Q_SLOTS: void reportError(const QString& message); void reportMessage(const QString& message); bool confirmMessage(const QString& message); - void createDisplayWindow(QThread* worker_thread, bool use_debug_device); + void createDisplayWindow(QThread* worker_thread, bool use_debug_device, bool fullscreen, bool render_to_main); void destroyDisplayWindow(); - void setFullscreen(bool fullscreen); - void toggleFullscreen(); + void updateDisplayWindow(bool fullscreen, bool render_to_main); void focusDisplayWidget(); void onEmulationStarted(); void onEmulationStopped(); diff --git a/src/duckstation-qt/qtdisplaywidget.cpp b/src/duckstation-qt/qtdisplaywidget.cpp index 6c8fcf178..ccd1affc9 100644 --- a/src/duckstation-qt/qtdisplaywidget.cpp +++ b/src/duckstation-qt/qtdisplaywidget.cpp @@ -145,6 +145,13 @@ bool QtDisplayWidget::event(QEvent* event) return true; } + case QEvent::Close: + { + m_host_interface->synchronousPowerOffSystem(); + QWidget::event(event); + return true; + } + case QEvent::WindowStateChange: { QWidget::event(event); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index f6b0c3757..2a01e17ab 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -67,11 +67,14 @@ void QtHostInterface::ReportError(const char* message) { HostInterface::ReportError(message); - emit setFullscreenRequested(false); + const bool was_fullscreen = m_is_fullscreen; + if (was_fullscreen) + SetFullscreen(false); + emit errorReported(QString::fromLocal8Bit(message)); - if (m_settings.start_fullscreen) - emit setFullscreenRequested(true); + if (was_fullscreen) + SetFullscreen(true); } void QtHostInterface::ReportMessage(const char* message) @@ -83,12 +86,14 @@ void QtHostInterface::ReportMessage(const char* message) bool QtHostInterface::ConfirmMessage(const char* message) { - emit setFullscreenRequested(false); + const bool was_fullscreen = m_is_fullscreen; + if (was_fullscreen) + SetFullscreen(false); const bool result = messageConfirmed(QString::fromLocal8Bit(message)); - if (m_settings.start_fullscreen) - emit setFullscreenRequested(true); + if (was_fullscreen) + SetFullscreen(true); return result; } @@ -137,6 +142,14 @@ void QtHostInterface::applySettings() QtSettingsInterface si(m_qsettings); UpdateSettings([this, &si]() { m_settings.Load(si); }); CommonHostInterface::UpdateInputMap(si); + + // detect when render-to-main flag changes + const bool render_to_main = m_qsettings.value("Main/RenderToMainWindow", true).toBool(); + if (m_system && m_display_widget && !m_is_fullscreen && render_to_main != m_is_rendering_to_main) + { + m_is_rendering_to_main = render_to_main; + emit updateDisplayWindowRequested(false, render_to_main); + } } void QtHostInterface::loadSettings() @@ -257,11 +270,25 @@ void QtHostInterface::redrawDisplayWindow() renderDisplay(); } +void QtHostInterface::toggleFullscreen() +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "toggleFullscreen", Qt::QueuedConnection); + return; + } + + ToggleFullscreen(); +} + bool QtHostInterface::AcquireHostDisplay() { DebugAssert(!m_display_widget); - emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device); + m_is_rendering_to_main = getSettingValue("Main/RenderToMainWindow", true).toBool(); + m_is_fullscreen = m_settings.start_fullscreen; + emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device, m_is_fullscreen, + m_is_rendering_to_main); if (!m_display_widget->hasDeviceContext()) { m_display_widget = nullptr; @@ -292,12 +319,17 @@ void QtHostInterface::ReleaseHostDisplay() void QtHostInterface::SetFullscreen(bool enabled) { - emit setFullscreenRequested(enabled); + if (m_is_fullscreen == enabled) + return; + + m_is_fullscreen = enabled; + emit updateDisplayWindowRequested(m_is_fullscreen, m_is_rendering_to_main); } void QtHostInterface::ToggleFullscreen() { - emit toggleFullscreenRequested(); + m_is_fullscreen = !m_is_fullscreen; + emit updateDisplayWindowRequested(m_is_fullscreen, m_is_rendering_to_main); } std::optional QtHostInterface::GetHostKeyCode(const std::string_view key_code) const @@ -381,6 +413,13 @@ void QtHostInterface::OnSystemStateSaved(bool global, s32 slot) emit stateSaved(QString::fromStdString(m_system->GetRunningCode()), global, slot); } +void QtHostInterface::SetDefaultSettings(SettingsInterface& si) +{ + CommonHostInterface::SetDefaultSettings(si); + + si.SetBoolValue("Main", "RenderToMainWindow", true); +} + void QtHostInterface::UpdateInputMap() { updateInputMap(); @@ -746,7 +785,7 @@ void QtHostInterface::threadEntryPoint() } Shutdown(); - + delete m_worker_thread_event_loop; m_worker_thread_event_loop = nullptr; diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index f5dc49173..b82115245 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -75,10 +75,10 @@ Q_SIGNALS: void emulationPaused(bool paused); void stateSaved(const QString& game_code, bool global, qint32 slot); void gameListRefreshed(); - void createDisplayWindowRequested(QThread* worker_thread, bool use_debug_device); + void createDisplayWindowRequested(QThread* worker_thread, bool use_debug_device, bool fullscreen, + bool render_to_main); void destroyDisplayWindowRequested(); - void setFullscreenRequested(bool fullscreen); - void toggleFullscreenRequested(); + void updateDisplayWindowRequested(bool fullscreen, bool render_to_main); void focusDisplayWidgetRequested(); void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time); @@ -103,6 +103,7 @@ public Q_SLOTS: void stopDumpingAudio(); void saveScreenshot(); void redrawDisplayWindow(); + void toggleFullscreen(); /// Enables controller polling even without a system active. Must be matched by a call to /// disableBackgroundControllerPolling. @@ -131,7 +132,8 @@ protected: void OnRunningGameChanged() override; void OnSystemStateSaved(bool global, s32 slot) override; - void UpdateInputMap() override; + void SetDefaultSettings(SettingsInterface& si) override; + void UpdateInputMap() override; private: enum : u32 @@ -182,4 +184,7 @@ private: QTimer* m_background_controller_polling_timer = nullptr; u32 m_background_controller_polling_enable_count = 0; + + bool m_is_rendering_to_main = false; + bool m_is_fullscreen = false; };