Qt: Add option to show log in main window

When using render to separate window. Makes the main window actually
useful.
This commit is contained in:
Stenzek
2025-12-20 15:54:42 +10:00
parent 962137fcdf
commit b6cd37c6a4
7 changed files with 144 additions and 47 deletions

View File

@@ -78,6 +78,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hideMainWindow, "Main", "HideMainWindowWhenRunning", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableWindowResizing, "Main", "DisableWindowResize", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hideMouseCursor, "Main", "HideCursorInFullscreen", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.displayLogInMainWindow, "Main", "DisplayLogInMainWindow",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.createSaveStateBackups, "Main", "CreateSaveStateBackups",
Settings::DEFAULT_SAVE_STATE_BACKUPS);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDiscordPresence, "Main", "EnableDiscordPresence", false);
@@ -184,6 +186,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
dialog->registerWidgetHelp(
m_ui.hideMainWindow, tr("Hide Main Window When Running"), tr("Unchecked"),
tr("Hides the main window of the application while the game is displayed in a separate window."));
dialog->registerWidgetHelp(m_ui.displayLogInMainWindow, tr("Display System Log In Main Window"), tr("Unchecked"),
tr("Displays the log in the main window of the application while a game is running."));
dialog->registerWidgetHelp(m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"),
tr("Prevents resizing of the window while a game is running."));
dialog->registerWidgetHelp(m_ui.automaticallyResizeWindow, tr("Automatically Resize Window"), tr("Unchecked"),
@@ -259,7 +263,9 @@ void InterfaceSettingsWidget::populateLanguageDropdown(QComboBox* cb)
void InterfaceSettingsWidget::updateRenderToSeparateWindowOptions()
{
const bool render_to_separate_window = m_dialog->getEffectiveBoolValue("Main", "RenderToSeparateWindow", false);
const bool hide_main_window = m_dialog->getEffectiveBoolValue("Main", "HideMainWindowWhenRunning", false);
m_ui.hideMainWindow->setEnabled(render_to_separate_window);
m_ui.displayLogInMainWindow->setEnabled(render_to_separate_window && !hide_main_window);
}
void InterfaceSettingsWidget::onLanguageChanged()

View File

@@ -130,9 +130,9 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="automaticallyResizeWindow">
<widget class="QCheckBox" name="displayLogInMainWindow">
<property name="text">
<string>Automatically Resize Window</string>
<string>Display System Log In Main Window</string>
</property>
</widget>
</item>
@@ -143,6 +143,13 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="automaticallyResizeWindow">
<property name="text">
<string>Automatically Resize Window</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -222,7 +222,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api,
bool exclusive_fullscreen, Error* error)
{
const bool render_to_main =
QtHost::CanRenderToMainWindow() && !fullscreen && (s_locals.system_locked.load(std::memory_order_relaxed) == 0);
canRenderToMainWindow() && !fullscreen && (s_locals.system_locked.load(std::memory_order_relaxed) == 0);
DEV_LOG("acquireRenderWindow() fullscreen={} exclusive_fullscreen={}, render_to_main={}", fullscreen,
exclusive_fullscreen, render_to_main);
@@ -283,10 +283,29 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api,
updateWindowTitle();
updateWindowState();
updateLogWidget();
return wi;
}
bool MainWindow::canRenderToMainWindow() const
{
return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && !QtHost::InNoGUIMode();
}
bool MainWindow::useMainWindowGeometryForDisplayWindow() const
{
// nogui _or_ main window mode, since we want to use it for temporary unfullscreens
return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) || QtHost::InNoGUIMode();
}
bool MainWindow::wantsLogWidget() const
{
return (wantsDisplayWidget() && Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) &&
!Host::GetBaseBoolSettingValue("Main", "HideMainWindowWhenRunning", false) &&
Host::GetBoolSettingValue("Main", "DisplayLogInMainWindow", false));
}
bool MainWindow::wantsDisplayWidget() const
{
// big picture or system created
@@ -295,7 +314,7 @@ bool MainWindow::wantsDisplayWidget() const
bool MainWindow::hasDisplayWidget() const
{
return m_display_widget != nullptr;
return (m_display_widget != nullptr);
}
void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
@@ -327,7 +346,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
if (fullscreen)
{
if (isVisible() && QtHost::CanRenderToMainWindow())
if (isVisible() && canRenderToMainWindow())
container->move(pos());
else
restoreDisplayWindowGeometryFromConfig();
@@ -346,9 +365,9 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
}
else
{
AssertMsg(m_ui.mainContainer->count() == 1, "Has no display widget");
m_ui.mainContainer->addWidget(container);
m_ui.mainContainer->setCurrentIndex(1);
AssertMsg(m_ui.mainContainer->indexOf(m_display_widget) < 0, "Has no display widget");
m_ui.mainContainer->addWidget(m_display_widget);
m_ui.mainContainer->setCurrentWidget(m_display_widget);
m_ui.actionViewSystemDisplay->setChecked(true);
}
@@ -389,6 +408,7 @@ void MainWindow::releaseRenderWindow()
destroyDisplayWidget();
updateWindowTitle();
updateWindowState();
updateLogWidget();
}
void MainWindow::destroyDisplayWidget()
@@ -403,9 +423,9 @@ void MainWindow::destroyDisplayWidget()
if (isRenderingToMain())
{
AssertMsg(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack");
AssertMsg(m_ui.mainContainer->indexOf(m_display_widget) >= 0, "Display widget in stack");
m_ui.mainContainer->removeWidget(m_display_widget);
m_ui.mainContainer->setCurrentIndex(0);
m_ui.mainContainer->setCurrentWidget(m_game_list_widget);
if (m_game_list_widget->isShowingGameGrid())
m_ui.actionViewGameGrid->setChecked(true);
else
@@ -447,8 +467,8 @@ void MainWindow::updateDisplayRelatedActions()
const bool fullscreen = isRenderingFullscreen();
// rendering to main, or switched to gamelist/grid
m_ui.actionViewSystemDisplay->setEnabled(wantsDisplayWidget() && QtHost::CanRenderToMainWindow() &&
!s_locals.system_starting);
m_ui.actionViewSystemDisplay->setEnabled(isRenderingToMain());
m_ui.actionViewSystemLog->setEnabled(m_log_widget != nullptr);
m_ui.menuWindowSize->setEnabled(s_locals.system_valid && !s_locals.system_starting && m_display_widget &&
!fullscreen);
m_ui.actionFullscreen->setEnabled(m_display_widget && !s_locals.system_starting);
@@ -480,6 +500,35 @@ void MainWindow::updateGameListRelatedActions()
m_ui.actionClearGameListBackground->setDisabled(disable || !has_background);
}
void MainWindow::updateLogWidget()
{
const bool has_log_widget = (m_log_widget != nullptr);
const bool wants_log_widget = wantsLogWidget();
if (has_log_widget == wants_log_widget)
return;
m_ui.actionViewSystemLog->setEnabled(wants_log_widget);
if (has_log_widget && !wants_log_widget)
{
// avoid focusing log widget
if (!isRenderingToMain())
switchToGameListView(false);
DEV_COLOR_LOG(StrongMagenta, "Removing main window log widget");
m_ui.mainContainer->removeWidget(m_log_widget);
QtUtils::SafeDeleteWidget(m_log_widget);
}
else if (!has_log_widget && wants_log_widget)
{
DEV_COLOR_LOG(StrongMagenta, "Creating main window log widget");
m_log_widget = new LogWidget(this);
m_ui.mainContainer->addWidget(m_log_widget);
DebugAssert(m_ui.mainContainer->indexOf(m_log_widget) >= 0);
switchToEmulationView();
}
}
QWidget* MainWindow::getDisplayContainer() const
{
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
@@ -498,6 +547,7 @@ void MainWindow::onSystemStarting()
s_locals.system_valid = false;
s_locals.system_paused = false;
updateLogWidget();
switchToEmulationView();
updateEmulationActions();
updateDisplayRelatedActions();
@@ -553,6 +603,7 @@ void MainWindow::onSystemStopping()
void MainWindow::onSystemDestroyed()
{
Assert(!QtHost::IsSystemValidOrStarting());
updateLogWidget();
// If we're closing or in batch mode, quit the whole application now.
if (m_is_closing || QtHost::InBatchMode())
@@ -1739,6 +1790,7 @@ void MainWindow::setupAdditionalUi()
group->addAction(m_ui.actionViewGameList);
group->addAction(m_ui.actionViewGameGrid);
group->addAction(m_ui.actionViewSystemDisplay);
group->addAction(m_ui.actionViewSystemLog);
m_game_list_widget =
new GameListWidget(m_ui.mainContainer, m_ui.actionViewGameList, m_ui.actionViewGameGrid, m_ui.actionMergeDiscSets,
@@ -2265,7 +2317,7 @@ void MainWindow::clearProgressBar()
bool MainWindow::isShowingGameList() const
{
return (m_ui.mainContainer->currentIndex() == 0);
return (m_ui.mainContainer->currentWidget() == m_game_list_widget);
}
bool MainWindow::isRenderingFullscreen() const
@@ -2275,7 +2327,7 @@ bool MainWindow::isRenderingFullscreen() const
bool MainWindow::isRenderingToMain() const
{
return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) == 1);
return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) >= 0);
}
bool MainWindow::shouldHideMouseCursor() const
@@ -2290,15 +2342,15 @@ bool MainWindow::shouldHideMainWindow() const
return (!isRenderingToMain() && wantsDisplayWidget() &&
((Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) &&
Host::GetBoolSettingValue("Main", "HideMainWindowWhenRunning", false)) ||
(QtHost::CanRenderToMainWindow() &&
(canRenderToMainWindow() &&
(isRenderingFullscreen() || s_locals.system_locked.load(std::memory_order_relaxed))))) ||
QtHost::InNoGUIMode();
}
void MainWindow::switchToGameListView()
void MainWindow::switchToGameListView(bool pause_system /* = true */)
{
// Normally, we'd never end up here. But on MacOS, the global menu is accessible while fullscreen.
if (QtHost::CanRenderToMainWindow() && isRenderingFullscreen())
if (canRenderToMainWindow() && isRenderingFullscreen())
{
g_emu_thread->setFullscreenWithCompletionHandler(false, []() { g_main_window->switchToGameListView(); });
return;
@@ -2308,10 +2360,10 @@ void MainWindow::switchToGameListView()
return;
m_was_paused_on_game_list_switch = s_locals.system_paused;
if (!s_locals.system_paused)
if (!s_locals.system_paused && pause_system)
g_emu_thread->setSystemPaused(true);
m_ui.mainContainer->setCurrentIndex(0);
m_ui.mainContainer->setCurrentWidget(m_game_list_widget);
if (m_game_list_widget->isShowingGameGrid())
m_ui.actionViewGameGrid->setChecked(true);
else
@@ -2323,15 +2375,28 @@ void MainWindow::switchToGameListView()
void MainWindow::switchToEmulationView()
{
if (!isRenderingToMain() || m_ui.mainContainer->currentWidget() == m_display_widget)
return;
if (isRenderingToMain())
{
if (!m_display_widget || m_ui.mainContainer->currentWidget() == m_display_widget)
return;
m_ui.mainContainer->setCurrentIndex(1);
m_ui.actionViewSystemDisplay->setChecked(true);
m_ui.mainContainer->setCurrentWidget(m_display_widget);
m_ui.actionViewSystemDisplay->setChecked(true);
// size of the widget might have changed, let it check itself
m_display_widget->checkForSizeChange();
m_display_widget->setFocus();
// size of the widget might have changed, let it check itself
m_display_widget->checkForSizeChange();
m_display_widget->setFocus();
}
else
{
if (!m_log_widget || m_ui.mainContainer->currentWidget() == m_log_widget)
return;
m_ui.mainContainer->setCurrentWidget(m_log_widget);
m_ui.actionViewSystemLog->setChecked(true);
m_log_widget->setFocus();
}
// resume if we weren't paused at switch time
if (s_locals.system_paused && !m_was_paused_on_game_list_switch)
@@ -2408,6 +2473,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered);
connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
connect(m_ui.actionViewSystemLog, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
connect(m_ui.actionViewGameProperties, &QAction::triggered, this, [this]() { openGamePropertiesForCurrentGame(); });
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
@@ -2635,7 +2701,7 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
DebugAssert(m_display_widget);
// just sync it with the main window if we're not using nogui modem, config will be stale
if (QtHost::CanRenderToMainWindow())
if (canRenderToMainWindow())
{
container->setGeometry(geometry());
return;
@@ -2643,7 +2709,7 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
// we don't want the temporary windowed window to be positioned on a different monitor, so use the main window
// coordinates... unless you're on wayland, too fucking bad, broken by design.
const bool use_main_window_pos = QtHost::UseMainWindowGeometryForDisplayWindow();
const bool use_main_window_pos = useMainWindowGeometryForDisplayWindow();
m_display_widget->setWindowPositionKey(use_main_window_pos ? "MainWindow" : "DisplayWindow");
if (!QtUtils::RestoreWindowGeometry(m_display_widget->windowPositionKey(), container))
@@ -3018,8 +3084,15 @@ void MainWindow::checkForSettingChanges()
// don't change state if temporary unfullscreened
if (m_display_widget && !QtHost::IsSystemLocked() && !isRenderingFullscreen())
{
if (QtHost::CanRenderToMainWindow() != isRenderingToMain())
if (canRenderToMainWindow() != isRenderingToMain())
g_emu_thread->updateDisplayWindow();
else
updateLogWidget();
}
else
{
// log widget update must be deferred because updateDisplayWindow() is asynchronous
updateLogWidget();
}
LogWindow::updateSettings();

View File

@@ -25,6 +25,7 @@ class EmuThread;
class GameListWidget;
class DisplayWidget;
class DisplayContainer;
class LogWidget;
class SettingsWindow;
class ControllerSettingsWindow;
class AutoUpdaterDialog;
@@ -177,16 +178,26 @@ private:
bool shouldHideMouseCursor() const;
bool shouldHideMainWindow() const;
void switchToGameListView();
void switchToGameListView(bool pause_system = true);
void switchToEmulationView();
void saveDisplayWindowGeometryToConfig();
void restoreDisplayWindowGeometryFromConfig();
/// Returns true if rendering to the main window should be allowed.
bool canRenderToMainWindow() const;
/// Returns true if the separate-window display widget should use the main window coordinates.
bool useMainWindowGeometryForDisplayWindow() const;
bool wantsLogWidget() const;
bool wantsDisplayWidget() const;
void createDisplayWidget(bool fullscreen, bool render_to_main);
void destroyDisplayWidget();
void updateDisplayWidgetCursor();
void updateDisplayRelatedActions();
void updateGameListRelatedActions();
void updateLogWidget();
void doSettings(const char* category = nullptr);
void openGamePropertiesForCurrentGame(const char* category = nullptr);
@@ -313,6 +324,8 @@ private:
DisplayWidget* m_display_widget = nullptr;
DisplayContainer* m_display_container = nullptr;
LogWidget* m_log_widget = nullptr;
QProgressBar* m_status_progress_widget = nullptr;
QLabel* m_status_renderer_widget = nullptr;
QLabel* m_status_fps_widget = nullptr;

View File

@@ -211,6 +211,7 @@
<addaction name="actionViewGameList"/>
<addaction name="actionViewGameGrid"/>
<addaction name="actionViewSystemDisplay"/>
<addaction name="actionViewSystemLog"/>
<addaction name="separator"/>
<addaction name="actionFullscreen"/>
<addaction name="menuWindowSize"/>
@@ -1415,6 +1416,20 @@
<string>Downloads icons for all games from RetroAchievements.</string>
</property>
</action>
<action name="actionViewSystemLog">
<property name="checkable">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="file-list-line"/>
</property>
<property name="text">
<string>System Log</string>
</property>
</action>
</widget>
<resources>
<include location="resources/duckstation-qt.qrc"/>

View File

@@ -695,17 +695,6 @@ void QtHost::SetDefaultSettings(SettingsInterface& si, bool system, bool control
}
}
bool QtHost::CanRenderToMainWindow()
{
return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && !InNoGUIMode();
}
bool QtHost::UseMainWindowGeometryForDisplayWindow()
{
// nogui _or_ main window mode, since we want to use it for temporary unfullscreens
return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) || InNoGUIMode();
}
void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height)
{
emit g_emu_thread->onResizeRenderWindowRequested(new_window_width, new_window_height);

View File

@@ -321,12 +321,6 @@ bool InNoGUIMode();
/// Returns true if display widgets need to wrapped in a container, thanks to Wayland stupidity.
bool IsDisplayWidgetContainerNeeded();
/// Returns true if rendering to the main window should be allowed.
bool CanRenderToMainWindow();
/// Returns true if the separate-window display widget should use the main window coordinates.
bool UseMainWindowGeometryForDisplayWindow();
/// Call when the language changes.
void UpdateApplicationLanguage(QWidget* dialog_parent);