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.pauseOnStart, "Main/StartPaused");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startFullscreen, "Main/StartFullscreen"); 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.saveStateOnExit, "Main/SaveStateOnExit");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main/ConfirmPowerOff"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main/ConfirmPowerOff");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display/ShowOSDMessages"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display/ShowOSDMessages");
@ -29,16 +30,19 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
onEnableSpeedLimiterStateChanged(); onEnableSpeedLimiterStateChanged();
onEmulationSpeedValueChanged(m_ui.emulationSpeed->value()); 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", dialog->registerWidgetHelp(m_ui.confirmPowerOff, "Confirm Power Off", "Checked",
"Determines whether a prompt will be displayed to confirm shutting down the emulator/game " "Determines whether a prompt will be displayed to confirm shutting down the emulator/game "
"when the hotkey is pressed."); "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", dialog->registerWidgetHelp(m_ui.enableSpeedLimiter, "Enable Speed Limiter", "Checked",
"Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will " "Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will "
"run as fast as possible, which may not be playable."); "run as fast as possible, which may not be playable.");

View file

@ -33,30 +33,37 @@
</property> </property>
<layout class="QGridLayout" name="formLayout_4"> <layout class="QGridLayout" name="formLayout_4">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="pauseOnStart"> <widget class="QCheckBox" name="confirmPowerOff">
<property name="text"> <property name="text">
<string>Pause On Start</string> <string>Confirm Power Off</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <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"> <widget class="QCheckBox" name="saveStateOnExit">
<property name="text"> <property name="text">
<string>Save State On Exit</string> <string>Save State On Exit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="0">
<widget class="QCheckBox" name="confirmPowerOff"> <widget class="QCheckBox" name="startFullscreen">
<property name="text"> <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> </property>
</widget> </widget>
</item> </item>

View file

@ -60,18 +60,34 @@ bool MainWindow::confirmMessage(const QString& message)
return (result == QMessageBox::Yes); 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); DebugAssert(!m_display_widget);
m_display_widget = m_host_interface->createDisplayWidget(); m_display_widget = m_host_interface->createDisplayWidget();
m_display_widget->setWindowTitle(windowTitle());
m_display_widget->setWindowIcon(windowIcon());
DebugAssert(m_display_widget); DebugAssert(m_display_widget);
m_display_widget->setFocusPolicy(Qt::StrongFocus); 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); m_ui.mainContainer->insertWidget(1, m_display_widget);
switchToEmulationView();
}
// we need the surface visible.. this might be able to be replaced with something else // we need the surface visible.. this might be able to be replaced with something else
switchToEmulationView();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
m_display_widget->createDeviceContext(worker_thread, use_debug_device); m_display_widget->createDeviceContext(worker_thread, use_debug_device);
@ -81,49 +97,68 @@ void MainWindow::destroyDisplayWindow()
{ {
DebugAssert(m_display_widget); DebugAssert(m_display_widget);
const bool was_fullscreen = m_display_widget->isFullScreen(); if (m_display_widget->isFullScreen())
if (was_fullscreen) m_display_widget->showNormal();
toggleFullscreen();
if (m_display_widget->parent())
{
m_ui.mainContainer->removeWidget(m_display_widget);
switchToGameListView(); switchToGameListView();
}
// recreate the display widget using the potentially-new renderer // recreate the display widget using the potentially-new renderer
m_ui.mainContainer->removeWidget(m_display_widget);
delete m_display_widget; delete m_display_widget;
m_display_widget = nullptr; 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; return;
if (fullscreen) if (fullscreen || !render_to_main)
{
if (m_display_widget->parent())
{ {
m_ui.mainContainer->setCurrentIndex(0); m_ui.mainContainer->setCurrentIndex(0);
m_ui.mainContainer->removeWidget(m_display_widget); m_ui.mainContainer->removeWidget(m_display_widget);
m_display_widget->setParent(nullptr); m_display_widget->setParent(nullptr);
switchToGameListView();
}
if (fullscreen)
{
m_display_widget->showFullScreen(); m_display_widget->showFullScreen();
m_display_widget->setCursor(Qt::BlankCursor); m_display_widget->setCursor(Qt::BlankCursor);
} }
else 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->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->insertWidget(1, m_display_widget);
m_ui.mainContainer->setCurrentIndex(1); m_ui.mainContainer->setCurrentIndex(1);
} }
m_display_widget->setCursor(QCursor());
}
m_display_widget->setFocus(); m_display_widget->setFocus();
QSignalBlocker blocker(m_ui.actionFullscreen); QSignalBlocker blocker(m_ui.actionFullscreen);
m_ui.actionFullscreen->setChecked(fullscreen); m_ui.actionFullscreen->setChecked(fullscreen);
} }
void MainWindow::toggleFullscreen()
{
setFullscreen(!m_display_widget->isFullScreen());
}
void MainWindow::focusDisplayWidget() void MainWindow::focusDisplayWidget()
{ {
if (m_ui.mainContainer->currentIndex() != 1) if (m_ui.mainContainer->currentIndex() != 1)
@ -176,6 +211,9 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga
setWindowTitle(tr("DuckStation")); setWindowTitle(tr("DuckStation"));
else else
setWindowTitle(game_title); setWindowTitle(game_title);
if (m_display_widget)
m_display_widget->setWindowTitle(windowTitle());
} }
void MainWindow::onStartDiscActionTriggered() void MainWindow::onStartDiscActionTriggered()
@ -419,6 +457,7 @@ void MainWindow::switchToGameListView()
void MainWindow::switchToEmulationView() void MainWindow::switchToEmulationView()
{ {
if (m_display_widget->parent())
m_ui.mainContainer->setCurrentIndex(1); m_ui.mainContainer->setCurrentIndex(1);
m_display_widget->setFocus(); 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.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.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); });
connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close); 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.actionSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::Count); });
connect(m_ui.actionGeneralSettings, &QAction::triggered, connect(m_ui.actionGeneralSettings, &QAction::triggered,
[this]() { doSettings(SettingsDialog::Category::GeneralSettings); }); [this]() { doSettings(SettingsDialog::Category::GeneralSettings); });
@ -472,8 +511,8 @@ void MainWindow::connectSignals()
connect(m_host_interface, &QtHostInterface::createDisplayWindowRequested, this, &MainWindow::createDisplayWindow, connect(m_host_interface, &QtHostInterface::createDisplayWindowRequested, this, &MainWindow::createDisplayWindow,
Qt::BlockingQueuedConnection); Qt::BlockingQueuedConnection);
connect(m_host_interface, &QtHostInterface::destroyDisplayWindowRequested, this, &MainWindow::destroyDisplayWindow); connect(m_host_interface, &QtHostInterface::destroyDisplayWindowRequested, this, &MainWindow::destroyDisplayWindow);
connect(m_host_interface, &QtHostInterface::setFullscreenRequested, this, &MainWindow::setFullscreen); connect(m_host_interface, &QtHostInterface::updateDisplayWindowRequested, this, &MainWindow::updateDisplayWindow,
connect(m_host_interface, &QtHostInterface::toggleFullscreenRequested, this, &MainWindow::toggleFullscreen); Qt::BlockingQueuedConnection);
connect(m_host_interface, &QtHostInterface::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget); connect(m_host_interface, &QtHostInterface::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget);
connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted); connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted);
connect(m_host_interface, &QtHostInterface::emulationStopped, this, &MainWindow::onEmulationStopped); connect(m_host_interface, &QtHostInterface::emulationStopped, this, &MainWindow::onEmulationStopped);

View file

@ -28,10 +28,9 @@ private Q_SLOTS:
void reportError(const QString& message); void reportError(const QString& message);
void reportMessage(const QString& message); void reportMessage(const QString& message);
bool confirmMessage(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 destroyDisplayWindow();
void setFullscreen(bool fullscreen); void updateDisplayWindow(bool fullscreen, bool render_to_main);
void toggleFullscreen();
void focusDisplayWidget(); void focusDisplayWidget();
void onEmulationStarted(); void onEmulationStarted();
void onEmulationStopped(); void onEmulationStopped();

View file

@ -145,6 +145,13 @@ bool QtDisplayWidget::event(QEvent* event)
return true; return true;
} }
case QEvent::Close:
{
m_host_interface->synchronousPowerOffSystem();
QWidget::event(event);
return true;
}
case QEvent::WindowStateChange: case QEvent::WindowStateChange:
{ {
QWidget::event(event); QWidget::event(event);

View file

@ -67,11 +67,14 @@ void QtHostInterface::ReportError(const char* message)
{ {
HostInterface::ReportError(message); HostInterface::ReportError(message);
emit setFullscreenRequested(false); const bool was_fullscreen = m_is_fullscreen;
if (was_fullscreen)
SetFullscreen(false);
emit errorReported(QString::fromLocal8Bit(message)); emit errorReported(QString::fromLocal8Bit(message));
if (m_settings.start_fullscreen) if (was_fullscreen)
emit setFullscreenRequested(true); SetFullscreen(true);
} }
void QtHostInterface::ReportMessage(const char* message) void QtHostInterface::ReportMessage(const char* message)
@ -83,12 +86,14 @@ void QtHostInterface::ReportMessage(const char* message)
bool QtHostInterface::ConfirmMessage(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)); const bool result = messageConfirmed(QString::fromLocal8Bit(message));
if (m_settings.start_fullscreen) if (was_fullscreen)
emit setFullscreenRequested(true); SetFullscreen(true);
return result; return result;
} }
@ -137,6 +142,14 @@ void QtHostInterface::applySettings()
QtSettingsInterface si(m_qsettings); QtSettingsInterface si(m_qsettings);
UpdateSettings([this, &si]() { m_settings.Load(si); }); UpdateSettings([this, &si]() { m_settings.Load(si); });
CommonHostInterface::UpdateInputMap(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() void QtHostInterface::loadSettings()
@ -257,11 +270,25 @@ void QtHostInterface::redrawDisplayWindow()
renderDisplay(); renderDisplay();
} }
void QtHostInterface::toggleFullscreen()
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "toggleFullscreen", Qt::QueuedConnection);
return;
}
ToggleFullscreen();
}
bool QtHostInterface::AcquireHostDisplay() bool QtHostInterface::AcquireHostDisplay()
{ {
DebugAssert(!m_display_widget); 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()) if (!m_display_widget->hasDeviceContext())
{ {
m_display_widget = nullptr; m_display_widget = nullptr;
@ -292,12 +319,17 @@ void QtHostInterface::ReleaseHostDisplay()
void QtHostInterface::SetFullscreen(bool enabled) 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() 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 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); 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() void QtHostInterface::UpdateInputMap()
{ {
updateInputMap(); updateInputMap();

View file

@ -75,10 +75,10 @@ Q_SIGNALS:
void emulationPaused(bool paused); void emulationPaused(bool paused);
void stateSaved(const QString& game_code, bool global, qint32 slot); void stateSaved(const QString& game_code, bool global, qint32 slot);
void gameListRefreshed(); 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 destroyDisplayWindowRequested();
void setFullscreenRequested(bool fullscreen); void updateDisplayWindowRequested(bool fullscreen, bool render_to_main);
void toggleFullscreenRequested();
void focusDisplayWidgetRequested(); void focusDisplayWidgetRequested();
void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time,
float worst_frame_time); float worst_frame_time);
@ -103,6 +103,7 @@ public Q_SLOTS:
void stopDumpingAudio(); void stopDumpingAudio();
void saveScreenshot(); void saveScreenshot();
void redrawDisplayWindow(); void redrawDisplayWindow();
void toggleFullscreen();
/// Enables controller polling even without a system active. Must be matched by a call to /// Enables controller polling even without a system active. Must be matched by a call to
/// disableBackgroundControllerPolling. /// disableBackgroundControllerPolling.
@ -131,6 +132,7 @@ protected:
void OnRunningGameChanged() override; void OnRunningGameChanged() override;
void OnSystemStateSaved(bool global, s32 slot) override; void OnSystemStateSaved(bool global, s32 slot) override;
void SetDefaultSettings(SettingsInterface& si) override;
void UpdateInputMap() override; void UpdateInputMap() override;
private: private:
@ -182,4 +184,7 @@ private:
QTimer* m_background_controller_polling_timer = nullptr; QTimer* m_background_controller_polling_timer = nullptr;
u32 m_background_controller_polling_enable_count = 0; u32 m_background_controller_polling_enable_count = 0;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
}; };