Qt: Implement render-to-seperate-window and render-to-main toggle

This commit is contained in:
Connor McLaughlin 2020-04-05 22:58:47 +10:00
parent abb87f497f
commit bf6c1c4866
7 changed files with 163 additions and 63 deletions

View file

@ -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.");

View file

@ -33,30 +33,37 @@
</property>
<layout class="QGridLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QCheckBox" name="pauseOnStart">
<widget class="QCheckBox" name="confirmPowerOff">
<property name="text">
<string>Pause On Start</string>
<string>Confirm Power Off</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="startFullscreen">
<property name="text">
<string>Start Fullscreen</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="saveStateOnExit">
<property name="text">
<string>Save State On Exit</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="confirmPowerOff">
<item row="1" column="0">
<widget class="QCheckBox" name="startFullscreen">
<property name="text">
<string>Confirm Power Off</string>
<string>Start Fullscreen</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="renderToMain">
<property name="text">
<string>Render To Main Window</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="pauseOnStart">
<property name="text">
<string>Pause On Start</string>
</property>
</widget>
</item>

View file

@ -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);
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,49 +97,68 @@ 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();
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)
{
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_display_widget->setFocus();
QSignalBlocker blocker(m_ui.actionFullscreen);
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,6 +457,7 @@ void MainWindow::switchToGameListView()
void MainWindow::switchToEmulationView()
{
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);

View file

@ -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();

View file

@ -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);

View file

@ -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<CommonHostInterface::HostKeyCode> 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();

View file

@ -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,6 +132,7 @@ protected:
void OnRunningGameChanged() override;
void OnSystemStateSaved(bool global, s32 slot) override;
void SetDefaultSettings(SettingsInterface& si) override;
void UpdateInputMap() override;
private:
@ -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;
};