diff --git a/.github/workflows/scripts/common/kddockwidgets-dodgy-include.patch b/.github/workflows/scripts/common/kddockwidgets-dodgy-include.patch new file mode 100644 index 0000000000..2246f10695 --- /dev/null +++ b/.github/workflows/scripts/common/kddockwidgets-dodgy-include.patch @@ -0,0 +1,13 @@ +diff --git a/src/core/indicators/ClassicDropIndicatorOverlay.h b/src/core/indicators/ClassicDropIndicatorOverlay.h +index 2dfb9718a..9b01f002e 100644 +--- a/src/core/indicators/ClassicDropIndicatorOverlay.h ++++ b/src/core/indicators/ClassicDropIndicatorOverlay.h +@@ -11,7 +11,7 @@ + + #pragma once + +-#include "core/DropIndicatorOverlay.h" ++#include + + namespace KDDockWidgets { + diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh index fb4080f6e3..cc854588fb 100755 --- a/.github/workflows/scripts/linux/build-dependencies-qt.sh +++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh @@ -240,6 +240,7 @@ echo "Building KDDockWidgets..." rm -fr "KDDockWidgets-$KDDOCKWIDGETS" tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" cd "KDDockWidgets-$KDDOCKWIDGETS" +patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja cmake --build build --parallel ninja -C build install diff --git a/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json b/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json index b97fe7b4fc..f10cb70050 100644 --- a/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json +++ b/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json @@ -17,6 +17,10 @@ "tag": "v2.2.1", "commit": "3aaccddc00a11a643e0959a24677838993de15ac", "disable-submodules": true + }, + { + "type": "patch", + "path": "../../../common/kddockwidgets-dodgy-include.patch" } ], "cleanup": [ diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh index f1155afbce..b1a34ad4e9 100755 --- a/.github/workflows/scripts/macos/build-dependencies-universal.sh +++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh @@ -373,6 +373,7 @@ echo "Building KDDockWidgets..." rm -fr "KDDockWidgets-$KDDOCKWIDGETS" tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" cd "KDDockWidgets-$KDDOCKWIDGETS" +patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch" cmake "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_UNIVERSAL" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build cmake --build build --parallel cmake --install build diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh index 2fb14f300b..bbc13603c5 100755 --- a/.github/workflows/scripts/macos/build-dependencies.sh +++ b/.github/workflows/scripts/macos/build-dependencies.sh @@ -331,6 +331,7 @@ echo "Building KDDockWidgets..." rm -fr "KDDockWidgets-$KDDOCKWIDGETS" tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" cd "KDDockWidgets-$KDDOCKWIDGETS" +patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch" cmake "${CMAKE_COMMON[@]}" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build cmake --build build --parallel cmake --install build diff --git a/.github/workflows/scripts/windows/build-dependencies-arm64.bat b/.github/workflows/scripts/windows/build-dependencies-arm64.bat index 7551b872dd..2daafd7ce8 100644 --- a/.github/workflows/scripts/windows/build-dependencies-arm64.bat +++ b/.github/workflows/scripts/windows/build-dependencies-arm64.bat @@ -247,6 +247,7 @@ echo "Building KDDockWidgets..." rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%" %SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error +%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error cmake %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error cmake --build build --parallel || goto error ninja -C build install || goto error diff --git a/.github/workflows/scripts/windows/build-dependencies.bat b/.github/workflows/scripts/windows/build-dependencies.bat index 7bebf683f1..f23d1ecc36 100644 --- a/.github/workflows/scripts/windows/build-dependencies.bat +++ b/.github/workflows/scripts/windows/build-dependencies.bat @@ -251,6 +251,7 @@ echo "Building KDDockWidgets..." rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%" %SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error +%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error cmake --build build --parallel || goto error ninja -C build install || goto error diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 806ad2e65c..322f23ee62 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -86,6 +86,9 @@ target_sources(pcsx2-qt PRIVATE Settings/DebugAnalysisSettingsWidget.cpp Settings/DebugAnalysisSettingsWidget.h Settings/DebugAnalysisSettingsWidget.ui + Settings/DebugUserInterfaceSettingsWidget.cpp + Settings/DebugUserInterfaceSettingsWidget.h + Settings/DebugUserInterfaceSettingsWidget.ui Settings/DebugSettingsWidget.cpp Settings/DebugSettingsWidget.h Settings/DebugSettingsWidget.ui @@ -199,6 +202,8 @@ target_sources(pcsx2-qt PRIVATE Debugger/Docking/DockUtils.h Debugger/Docking/DockViews.cpp Debugger/Docking/DockViews.h + Debugger/Docking/DropIndicators.cpp + Debugger/Docking/DropIndicators.h Debugger/Docking/LayoutEditorDialog.cpp Debugger/Docking/LayoutEditorDialog.h Debugger/Docking/LayoutEditorDialog.ui diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp index 93fb6f2ad8..01903fdac4 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.cpp +++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp @@ -150,6 +150,11 @@ void DebuggerWindow::destroyInstance() g_debugger_window->close(); } +bool DebuggerWindow::shouldShowOnStartup() +{ + return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "ShowOnStartup", false); +} + DockManager& DebuggerWindow::dockManager() { return *m_dock_manager; @@ -253,7 +258,11 @@ void DebuggerWindow::updateStyleSheets() void DebuggerWindow::saveWindowGeometry() { std::string old_geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry"); - std::string geometry = saveGeometry().toBase64().toStdString(); + + std::string geometry; + if (shouldSaveWindowGeometry()) + geometry = saveGeometry().toBase64().toStdString(); + if (geometry != old_geometry) { Host::SetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry", geometry.c_str()); @@ -263,10 +272,18 @@ void DebuggerWindow::saveWindowGeometry() void DebuggerWindow::restoreWindowGeometry() { + if (!shouldSaveWindowGeometry()) + return; + std::string geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry"); restoreGeometry(QByteArray::fromBase64(QByteArray::fromStdString(geometry))); } +bool DebuggerWindow::shouldSaveWindowGeometry() +{ + return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "SaveWindowGeometry", true); +} + void DebuggerWindow::onVMStarting() { m_ui.actionRun->setEnabled(true); diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h index 2ed13a44fd..40cd195216 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.h +++ b/pcsx2-qt/Debugger/DebuggerWindow.h @@ -21,6 +21,7 @@ public: static DebuggerWindow* getInstance(); static DebuggerWindow* createInstance(); static void destroyInstance(); + static bool shouldShowOnStartup(); DockManager& dockManager(); @@ -34,6 +35,7 @@ public: void saveWindowGeometry(); void restoreWindowGeometry(); + bool shouldSaveWindowGeometry(); public slots: void onVMStarting(); diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index 61df141625..80a6e66328 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -831,16 +831,8 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon QColor DisassemblyWidget::GetAddressFunctionColor(u32 address) { - // This is an attempt to figure out if the current palette is dark or light - // We calculate the luminance of the alternateBase colour - // and swap between our darker and lighter function colours - std::array colors; - const QColor base = this->palette().alternateBase().color(); - - const auto Y = (base.redF() * 0.33) + (0.5 * base.greenF()) + (0.16 * base.blueF()); - - if (Y > 0.5) + if (QtUtils::IsLightTheme(palette())) { colors = { QColor::fromRgba(0xFFFA3434), diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp index b71a95f403..e76b031d47 100644 --- a/pcsx2-qt/Debugger/Docking/DockManager.cpp +++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp @@ -7,6 +7,7 @@ #include "Debugger/DebuggerWindow.h" #include "Debugger/Docking/DockTables.h" #include "Debugger/Docking/DockViews.h" +#include "Debugger/Docking/DropIndicators.h" #include "Debugger/Docking/LayoutEditorDialog.h" #include "Debugger/Docking/NoLayoutsWidget.h" @@ -18,6 +19,7 @@ #include #include #include +#include #include #include @@ -38,22 +40,42 @@ DockManager::DockManager(QObject* parent) void DockManager::configureDockingSystem() { + std::string indicator_style = Host::GetBaseStringSettingValue( + "Debugger/UserInterface", "DropIndicatorStyle", "Classic"); + + if (indicator_style == "Segmented" || indicator_style == "Minimalistic") + { + KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Segmented; + DockSegmentedDropIndicatorOverlay::s_indicator_style = indicator_style; + } + else + { + KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Classic; + } + static bool done = false; if (done) return; KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets); - KDDockWidgets::Config::self().setFlags( + KDDockWidgets::Config& config = KDDockWidgets::Config::self(); + + config.setFlags( KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible | KDDockWidgets::Config::Flag_AlwaysShowTabs | KDDockWidgets::Config::Flag_AllowReorderTabs | KDDockWidgets::Config::Flag_TitleBarIsFocusable); - KDDockWidgets::Config::self().setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory); - KDDockWidgets::Config::self().setViewFactory(new DockViewFactory()); - KDDockWidgets::Config::self().setDragAboutToStartFunc(&DockManager::dragAboutToStart); - KDDockWidgets::Config::self().setStartDragDistance(std::max(QApplication::startDragDistance(), 32)); + // We set this flag regardless of whether or not the windowing system + // supports compositing since it's only used by the built-in docking + // indicator, and we only fall back to that if compositing is disabled. + config.setInternalFlags(KDDockWidgets::Config::InternalFlag_DisableTranslucency); + + config.setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory); + config.setViewFactory(new DockViewFactory()); + config.setDragAboutToStartFunc(&DockManager::dragAboutToStart); + config.setStartDragDistance(std::max(QApplication::startDragDistance(), 32)); done = true; } diff --git a/pcsx2-qt/Debugger/Docking/DockUtils.cpp b/pcsx2-qt/Debugger/Docking/DockUtils.cpp index 85ff2de03f..687dc4664a 100644 --- a/pcsx2-qt/Debugger/Docking/DockUtils.cpp +++ b/pcsx2-qt/Debugger/Docking/DockUtils.cpp @@ -3,6 +3,7 @@ #include "DockUtils.h" +#include #include #include #include diff --git a/pcsx2-qt/Debugger/Docking/DockViews.cpp b/pcsx2-qt/Debugger/Docking/DockViews.cpp index 47c85791e2..4298c6218f 100644 --- a/pcsx2-qt/Debugger/Docking/DockViews.cpp +++ b/pcsx2-qt/Debugger/Docking/DockViews.cpp @@ -3,16 +3,20 @@ #include "DockViews.h" +#include "QtUtils.h" #include "Debugger/DebuggerWidget.h" #include "Debugger/DebuggerWindow.h" #include "Debugger/Docking/DockManager.h" +#include "Debugger/Docking/DropIndicators.h" #include "DebugTools/DebugInterface.h" +#include #include #include #include +#include #include #include #include @@ -47,6 +51,27 @@ KDDockWidgets::Core::View* DockViewFactory::createTabBar( return new DockTabBar(tabBar, KDDockWidgets::QtCommon::View_qt::asQWidget(parent)); } +KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createClassicIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators, + KDDockWidgets::Core::View* parent) const +{ + return new DockDropIndicatorProxy(classic_indicators); +} + +KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createFallbackClassicIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators, + KDDockWidgets::Core::View* parent) const +{ + return KDDockWidgets::QtWidgets::ViewFactory::createClassicIndicatorWindow(classic_indicators, parent); +} + +KDDockWidgets::Core::View* DockViewFactory::createSegmentedDropIndicatorOverlayView( + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, + KDDockWidgets::Core::View* parent) const +{ + return new DockSegmentedDropIndicatorOverlay(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent)); +} + // ***************************************************************************** DockWidget::DockWidget( @@ -77,12 +102,12 @@ DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidg { } -void DockTitleBar::mouseDoubleClickEvent(QMouseEvent* ev) +void DockTitleBar::mouseDoubleClickEvent(QMouseEvent* event) { if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked()) - KDDockWidgets::QtWidgets::TitleBar::mouseDoubleClickEvent(ev); + KDDockWidgets::QtWidgets::TitleBar::mouseDoubleClickEvent(event); else - ev->ignore(); + event->ignore(); } // ***************************************************************************** @@ -269,10 +294,10 @@ DockTabBar::WidgetsFromTabIndexResult DockTabBar::widgetsFromTabIndex(int tab_in return {widget, dock_controller, dock_view}; } -void DockTabBar::mouseDoubleClickEvent(QMouseEvent* ev) +void DockTabBar::mouseDoubleClickEvent(QMouseEvent* event) { if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked()) - KDDockWidgets::QtWidgets::TabBar::mouseDoubleClickEvent(ev); + KDDockWidgets::QtWidgets::TabBar::mouseDoubleClickEvent(event); else - ev->ignore(); + event->ignore(); } diff --git a/pcsx2-qt/Debugger/Docking/DockViews.h b/pcsx2-qt/Debugger/Docking/DockViews.h index eea0ad85f9..317e14cf3f 100644 --- a/pcsx2-qt/Debugger/Docking/DockViews.h +++ b/pcsx2-qt/Debugger/Docking/DockViews.h @@ -3,14 +3,14 @@ #pragma once +#include "DebugTools/DebugInterface.h" + #include #include #include #include #include -#include "DebugTools/DebugInterface.h" - class DebuggerWidget; class DockManager; @@ -36,6 +36,18 @@ public: KDDockWidgets::Core::View* createTabBar( KDDockWidgets::Core::TabBar* tabBar, KDDockWidgets::Core::View* parent) const override; + + KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createClassicIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators, + KDDockWidgets::Core::View* parent) const override; + + KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createFallbackClassicIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators, + KDDockWidgets::Core::View* parent) const; + + KDDockWidgets::Core::View* createSegmentedDropIndicatorOverlayView( + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, + KDDockWidgets::Core::View* parent) const override; }; class DockWidget : public KDDockWidgets::QtWidgets::DockWidget @@ -61,7 +73,7 @@ public: DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent = nullptr); protected: - void mouseDoubleClickEvent(QMouseEvent* ev) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; }; class DockStack : public KDDockWidgets::QtWidgets::Stack @@ -74,7 +86,7 @@ public: void init() override; protected: - void mouseDoubleClickEvent(QMouseEvent* ev) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; }; class DockTabBar : public KDDockWidgets::QtWidgets::TabBar @@ -97,5 +109,5 @@ protected: void setCpuOverrideForTab(int tab_index, std::optional cpu_override); WidgetsFromTabIndexResult widgetsFromTabIndex(int tab_index); - void mouseDoubleClickEvent(QMouseEvent* ev) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; }; diff --git a/pcsx2-qt/Debugger/Docking/DropIndicators.cpp b/pcsx2-qt/Debugger/Docking/DropIndicators.cpp new file mode 100644 index 0000000000..a424bc80e6 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/DropIndicators.cpp @@ -0,0 +1,571 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DropIndicators.h" + +#include "QtUtils.h" +#include "Debugger/Docking/DockViews.h" + +#include "common/Assertions.h" + +#include +#include +#include +#include + +#include + +static std::pair pickNiceColours(const QPalette& palette, bool hovered) +{ + QColor fill = palette.highlight().color(); + QColor outline = palette.highlight().color(); + + if (QtUtils::IsLightTheme(palette)) + { + fill = fill.darker(200); + outline = outline.darker(200); + } + else + { + fill = fill.lighter(200); + outline = outline.lighter(200); + } + + fill.setAlpha(200); + outline.setAlpha(255); + + if (!hovered) + { + fill.setAlpha(fill.alpha() / 2); + outline.setAlpha(outline.alpha() / 2); + } + + return {fill, outline}; +} + +// ***************************************************************************** + +DockDropIndicatorProxy::DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators) + : m_classic_indicators(classic_indicators) +{ + recreateWindowIfNecessary(); +} + +DockDropIndicatorProxy::~DockDropIndicatorProxy() +{ + delete m_window; + delete m_fallback_window; +} + +void DockDropIndicatorProxy::setObjectName(const QString& name) +{ + window()->setObjectName(name); +} + +KDDockWidgets::DropLocation DockDropIndicatorProxy::hover(QPoint globalPos) +{ + return window()->hover(globalPos); +} + +QPoint DockDropIndicatorProxy::posForIndicator(KDDockWidgets::DropLocation loc) const +{ + return window()->posForIndicator(loc); +} + +void DockDropIndicatorProxy::updatePositions() +{ + // Check if a compositor is running whenever a drag starts. + recreateWindowIfNecessary(); + + window()->updatePositions(); +} + +void DockDropIndicatorProxy::raise() +{ + window()->raise(); +} + +void DockDropIndicatorProxy::setVisible(bool visible) +{ + window()->setVisible(visible); +} + +void DockDropIndicatorProxy::resize(QSize size) +{ + window()->resize(size); +} + +void DockDropIndicatorProxy::setGeometry(QRect rect) +{ + window()->setGeometry(rect); +} + +bool DockDropIndicatorProxy::isWindow() const +{ + return window()->isWindow(); +} + +void DockDropIndicatorProxy::updateIndicatorVisibility() +{ + window()->updateIndicatorVisibility(); +} + +KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window() +{ + if (!m_supports_compositing) + { + pxAssert(m_fallback_window); + return m_fallback_window; + } + + pxAssert(m_window); + return m_window; +} + +const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window() const +{ + if (!m_supports_compositing) + { + pxAssert(m_fallback_window); + return m_fallback_window; + } + + pxAssert(m_window); + return m_window; +} + +void DockDropIndicatorProxy::recreateWindowIfNecessary() +{ + bool supports_compositing = QtUtils::IsCompositorManagerRunning(); + if (supports_compositing == m_supports_compositing && (m_window || m_fallback_window)) + return; + + m_supports_compositing = supports_compositing; + + DockViewFactory* factory = static_cast(KDDockWidgets::Config::self().viewFactory()); + + if (supports_compositing) + { + if (!m_window) + m_window = new DockDropIndicatorWindow(m_classic_indicators); + + QWidget* old_window = dynamic_cast(m_fallback_window); + if (old_window) + { + m_window->setObjectName(old_window->objectName()); + m_window->setVisible(old_window->isVisible()); + m_window->setGeometry(old_window->geometry()); + } + + delete m_fallback_window; + m_fallback_window = nullptr; + } + else + { + if (!m_fallback_window) + m_fallback_window = factory->createFallbackClassicIndicatorWindow(m_classic_indicators, nullptr); + + QWidget* old_window = dynamic_cast(m_window); + if (old_window) + { + m_window->setObjectName(old_window->objectName()); + m_window->setVisible(old_window->isVisible()); + m_window->setGeometry(old_window->geometry()); + } + + delete m_window; + m_window = nullptr; + } +} + +// ***************************************************************************** + +static const constexpr int IND_LEFT = 0; +static const constexpr int IND_TOP = 1; +static const constexpr int IND_RIGHT = 2; +static const constexpr int IND_BOTTOM = 3; +static const constexpr int IND_CENTER = 4; +static const constexpr int IND_OUTER_LEFT = 5; +static const constexpr int IND_OUTER_TOP = 6; +static const constexpr int IND_OUTER_RIGHT = 7; +static const constexpr int IND_OUTER_BOTTOM = 8; + +static const constexpr int INDICATOR_SIZE = 40; +static const constexpr int INDICATOR_MARGIN = 10; + +static bool isWayland() +{ + return KDDockWidgets::Core::Platform::instance()->displayType() == + KDDockWidgets::Core::Platform::DisplayType::Wayland; +} + +static QWidget* parentForIndicatorWindow(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators) +{ + if (isWayland()) + return KDDockWidgets::QtCommon::View_qt::asQWidget(classic_indicators->view()); + + return nullptr; +} + +static Qt::WindowFlags flagsForIndicatorWindow() +{ + if (isWayland()) + return Qt::Widget; + + return Qt::Tool | Qt::BypassWindowManagerHint; +} + +DockDropIndicatorWindow::DockDropIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators) + : QWidget(parentForIndicatorWindow(classic_indicators), flagsForIndicatorWindow()) + , m_classic_indicators(classic_indicators) + , m_indicators({ + /* [IND_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Left, this), + /* [IND_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Top, this), + /* [IND_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Right, this), + /* [IND_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Bottom, this), + /* [IND_CENTER] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Center, this), + /* [IND_OUTER_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterLeft, this), + /* [IND_OUTER_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterTop, this), + /* [IND_OUTER_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterRight, this), + /* [IND_OUTER_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterBottom, this), + }) +{ + setWindowFlag(Qt::FramelessWindowHint, true); + + if (KDDockWidgets::Config::self().flags() & KDDockWidgets::Config::Flag_KeepAboveIfNotUtilityWindow) + setWindowFlag(Qt::WindowStaysOnTopHint, true); + + setAttribute(Qt::WA_TranslucentBackground); +} + +void DockDropIndicatorWindow::setObjectName(const QString& name) +{ + QWidget::setObjectName(name); +} + +KDDockWidgets::DropLocation DockDropIndicatorWindow::hover(QPoint globalPos) +{ + KDDockWidgets::DropLocation result = KDDockWidgets::DropLocation_None; + + for (DockDropIndicator* indicator : m_indicators) + { + if (indicator->isVisible()) + { + bool hovered = indicator->rect().contains(indicator->mapFromGlobal(globalPos)); + if (hovered != indicator->hovered) + { + indicator->hovered = hovered; + indicator->update(); + } + + if (hovered) + result = indicator->location; + } + } + + return result; +} + +QPoint DockDropIndicatorWindow::posForIndicator(KDDockWidgets::DropLocation loc) const +{ + for (DockDropIndicator* indicator : m_indicators) + if (indicator->location == loc) + return indicator->mapToGlobal(indicator->rect().center()); + + return QPoint(); +} + +void DockDropIndicatorWindow::updatePositions() +{ + DockDropIndicator* left = m_indicators[IND_LEFT]; + DockDropIndicator* top = m_indicators[IND_TOP]; + DockDropIndicator* right = m_indicators[IND_RIGHT]; + DockDropIndicator* bottom = m_indicators[IND_BOTTOM]; + DockDropIndicator* center = m_indicators[IND_CENTER]; + DockDropIndicator* outer_left = m_indicators[IND_OUTER_LEFT]; + DockDropIndicator* outer_top = m_indicators[IND_OUTER_TOP]; + DockDropIndicator* outer_right = m_indicators[IND_OUTER_RIGHT]; + DockDropIndicator* outer_bottom = m_indicators[IND_OUTER_BOTTOM]; + + QRect r = rect(); + int half_indicator_width = INDICATOR_SIZE / 2; + + outer_left->move(r.x() + INDICATOR_MARGIN, r.center().y() - half_indicator_width); + outer_bottom->move(r.center().x() - half_indicator_width, r.y() + height() - INDICATOR_SIZE - INDICATOR_MARGIN); + outer_top->move(r.center().x() - half_indicator_width, r.y() + INDICATOR_MARGIN); + outer_right->move(r.x() + width() - INDICATOR_SIZE - INDICATOR_MARGIN, r.center().y() - half_indicator_width); + + KDDockWidgets::Core::Group* hovered_group = m_classic_indicators->hoveredGroup(); + if (hovered_group) + { + QRect hoveredRect = hovered_group->view()->geometry(); + center->move(r.topLeft() + hoveredRect.center() - QPoint(half_indicator_width, half_indicator_width)); + top->move(center->pos() - QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN)); + right->move(center->pos() + QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0)); + bottom->move(center->pos() + QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN)); + left->move(center->pos() - QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0)); + } +} + +void DockDropIndicatorWindow::raise() +{ + QWidget::raise(); +} + +void DockDropIndicatorWindow::setVisible(bool is) +{ + QWidget::setVisible(is); +} + +void DockDropIndicatorWindow::resize(QSize size) +{ + QWidget::resize(size); +} + +void DockDropIndicatorWindow::setGeometry(QRect rect) +{ + QWidget::setGeometry(rect); +} + +bool DockDropIndicatorWindow::isWindow() const +{ + return QWidget::isWindow(); +} + +void DockDropIndicatorWindow::updateIndicatorVisibility() +{ + for (DockDropIndicator* indicator : m_indicators) + indicator->setVisible(m_classic_indicators->dropIndicatorVisible(indicator->location)); +} + +void DockDropIndicatorWindow::resizeEvent(QResizeEvent* ev) +{ + QWidget::resizeEvent(ev); + updatePositions(); +} + +// ***************************************************************************** + +DockDropIndicator::DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent) + : QWidget(parent) + , location(loc) +{ + setFixedSize(INDICATOR_SIZE, INDICATOR_SIZE); + setVisible(true); +} + +void DockDropIndicator::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + auto [fill, outline] = pickNiceColours(palette(), hovered); + + painter.setBrush(fill); + + QPen pen; + pen.setColor(outline); + pen.setWidth(2); + painter.setPen(pen); + + painter.drawRect(rect()); + + QRectF rf = rect().toRectF(); + + QRectF outer = rf.marginsRemoved(QMarginsF(4.f, 4.f, 4.f, 4.f)); + QPointF icon_position; + switch (location) + { + case KDDockWidgets::DropLocation_Left: + case KDDockWidgets::DropLocation_OutterLeft: + outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, outer.width() / 2.f, 0.f)); + icon_position = rf.marginsRemoved(QMarginsF(rf.width() / 2.f, 0.f, 0.f, 0.f)).center(); + break; + case KDDockWidgets::DropLocation_Top: + case KDDockWidgets::DropLocation_OutterTop: + outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, outer.width() / 2.f)); + icon_position = rf.marginsRemoved(QMarginsF(0.f, rf.width() / 2.f, 0.f, 0.f)).center(); + break; + case KDDockWidgets::DropLocation_Right: + case KDDockWidgets::DropLocation_OutterRight: + outer = outer.marginsRemoved(QMarginsF(outer.width() / 2.f, 0.f, 0.f, 0.f)); + icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, rf.width() / 2.f, 0.f)).center(); + break; + case KDDockWidgets::DropLocation_Bottom: + case KDDockWidgets::DropLocation_OutterBottom: + outer = outer.marginsRemoved(QMarginsF(0.f, outer.width() / 2.f, 0.f, 0.f)); + icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, rf.width() / 2.f)).center(); + break; + default: + { + } + } + + painter.drawRect(outer); + + float arrow_size = INDICATOR_SIZE / 10.f; + + QPolygonF arrow; + switch (location) + { + case KDDockWidgets::DropLocation_Left: + arrow = { + icon_position + QPointF(-arrow_size, 0.f), + icon_position + QPointF(arrow_size, arrow_size * 2.f), + icon_position + QPointF(arrow_size, -arrow_size * 2.f), + }; + break; + case KDDockWidgets::DropLocation_Top: + arrow = { + icon_position + QPointF(0.f, -arrow_size), + icon_position + QPointF(arrow_size * 2.f, arrow_size), + icon_position + QPointF(-arrow_size * 2.f, arrow_size), + }; + break; + case KDDockWidgets::DropLocation_Right: + arrow = { + icon_position + QPointF(arrow_size, 0.f), + icon_position + QPointF(-arrow_size, arrow_size * 2.f), + icon_position + QPointF(-arrow_size, -arrow_size * 2.f), + }; + break; + case KDDockWidgets::DropLocation_Bottom: + arrow = { + icon_position + QPointF(0.f, arrow_size), + icon_position + QPointF(arrow_size * 2.f, -arrow_size), + icon_position + QPointF(-arrow_size * 2.f, -arrow_size), + }; + break; + default: + { + } + } + + painter.drawPolygon(arrow); +} + +// ***************************************************************************** + +std::string DockSegmentedDropIndicatorOverlay::s_indicator_style; + +DockSegmentedDropIndicatorOverlay::DockSegmentedDropIndicatorOverlay( + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent) + : KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay(controller, parent) +{ +} + +void DockSegmentedDropIndicatorOverlay::paintEvent(QPaintEvent* event) +{ + if (s_indicator_style == "Minimalistic") + drawMinimalistic(); + else + drawSegmented(); +} + +void DockSegmentedDropIndicatorOverlay::drawSegmented() +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller = + asController(); + + const std::unordered_map& segments = controller->segments(); + + for (KDDockWidgets::DropLocation location : + {KDDockWidgets::DropLocation_Left, + KDDockWidgets::DropLocation_Top, + KDDockWidgets::DropLocation_Right, + KDDockWidgets::DropLocation_Bottom, + KDDockWidgets::DropLocation_Center, + KDDockWidgets::DropLocation_OutterLeft, + KDDockWidgets::DropLocation_OutterTop, + KDDockWidgets::DropLocation_OutterRight, + KDDockWidgets::DropLocation_OutterBottom}) + { + auto segment = segments.find(location); + if (segment == segments.end() || segment->second.size() < 2) + continue; + + bool hovered = segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill); + auto [fill, outline] = pickNiceColours(palette(), hovered); + + painter.setBrush(fill); + + QPen pen(outline); + pen.setWidth(1); + painter.setPen(pen); + + int margin = KDDockWidgets::Core::SegmentedDropIndicatorOverlay::s_segmentGirth * 2; + + // Make sure the rectangles don't intersect with each other. + QRect rect; + switch (location) + { + case KDDockWidgets::DropLocation_Top: + case KDDockWidgets::DropLocation_Bottom: + case KDDockWidgets::DropLocation_OutterTop: + case KDDockWidgets::DropLocation_OutterBottom: + { + rect = segment->second.boundingRect().marginsRemoved(QMargins(margin, 4, margin, 4)); + break; + } + case KDDockWidgets::DropLocation_Left: + case KDDockWidgets::DropLocation_Right: + case KDDockWidgets::DropLocation_OutterLeft: + case KDDockWidgets::DropLocation_OutterRight: + { + rect = segment->second.boundingRect().marginsRemoved(QMargins(4, margin, 4, margin)); + break; + } + default: + { + rect = segment->second.boundingRect().marginsRemoved(QMargins(4, 4, 4, 4)); + break; + } + } + + painter.drawRect(rect); + } +} + +void DockSegmentedDropIndicatorOverlay::drawMinimalistic() +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller = + asController(); + + const std::unordered_map& segments = controller->segments(); + + for (KDDockWidgets::DropLocation location : + {KDDockWidgets::DropLocation_Left, + KDDockWidgets::DropLocation_Top, + KDDockWidgets::DropLocation_Right, + KDDockWidgets::DropLocation_Bottom, + KDDockWidgets::DropLocation_Center, + KDDockWidgets::DropLocation_OutterLeft, + KDDockWidgets::DropLocation_OutterTop, + KDDockWidgets::DropLocation_OutterRight, + KDDockWidgets::DropLocation_OutterBottom}) + { + auto segment = segments.find(location); + if (segment == segments.end() || segment->second.size() < 2) + continue; + + if (!segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill)) + continue; + + auto [fill, outline] = pickNiceColours(palette(), true); + + painter.setBrush(fill); + + QPen pen(outline); + pen.setWidth(1); + painter.setPen(pen); + + painter.drawRect(segment->second.boundingRect()); + } +} diff --git a/pcsx2-qt/Debugger/Docking/DropIndicators.h b/pcsx2-qt/Debugger/Docking/DropIndicators.h new file mode 100644 index 0000000000..63297e6c03 --- /dev/null +++ b/pcsx2-qt/Debugger/Docking/DropIndicators.h @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include +#include +#include + +class DockDropIndicator; + +// This switches between our custom drop indicators and KDDockWidget's built-in +// ones on the fly depending on whether or not we have a windowing system that +// supports compositing. +class DockDropIndicatorProxy : public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface +{ +public: + DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators); + ~DockDropIndicatorProxy(); + + void setObjectName(const QString&) override; + KDDockWidgets::DropLocation hover(QPoint globalPos) override; + QPoint posForIndicator(KDDockWidgets::DropLocation) const override; + void updatePositions() override; + void raise() override; + void setVisible(bool visible) override; + void resize(QSize size) override; + void setGeometry(QRect rect) override; + bool isWindow() const override; + void updateIndicatorVisibility() override; + +private: + KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window(); + const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window() const; + + void recreateWindowIfNecessary(); + + KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_window = nullptr; + KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_fallback_window = nullptr; + + bool m_supports_compositing = true; + KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators = nullptr; +}; + +// Our default custom drop indicator implementation. This fits in with PCSX2's +// themes a lot better, but doesn't support windowing systems where compositing +// is disabled (it would show a black screen). +class DockDropIndicatorWindow : public QWidget, public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface +{ + Q_OBJECT + +public: + DockDropIndicatorWindow( + KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators); + + void setObjectName(const QString& name) override; + KDDockWidgets::DropLocation hover(QPoint globalPos) override; + QPoint posForIndicator(KDDockWidgets::DropLocation loc) const override; + void updatePositions() override; + void raise() override; + void setVisible(bool visible) override; + void resize(QSize size) override; + void setGeometry(QRect rect) override; + bool isWindow() const override; + void updateIndicatorVisibility() override; + +protected: + void resizeEvent(QResizeEvent* ev) override; + +private: + KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators; + std::vector m_indicators; +}; + +class DockDropIndicator : public QWidget +{ + Q_OBJECT + +public: + DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent = nullptr); + + KDDockWidgets::DropLocation location; + bool hovered = false; + +protected: + void paintEvent(QPaintEvent* event) override; +}; + +// An alternative drop indicator design that can be enabled from the settings +// menu. For this one we don't need to worry about whether compositing is +// supported since it doesn't create its own window. +class DockSegmentedDropIndicatorOverlay : public KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay +{ + Q_OBJECT + +public: + DockSegmentedDropIndicatorOverlay( + KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent = nullptr); + + static std::string s_indicator_style; + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + void drawSegmented(); + void drawMinimalistic(); +}; diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 0893122bae..1aca34a89f 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0+ #include "AutoUpdaterDialog.h" +#include "Debugger/DebuggerWindow.h" #include "DisplayWidget.h" #include "GameList/GameListWidget.h" #include "LogWindow.h" @@ -1064,12 +1065,12 @@ void EmuThread::updatePerformanceMetrics(bool force) Q_ARG(const QString&, tr("VPS: %1 ").arg(vfps, 0, 'f', 0))); m_last_video_fps = vfps; - if (speed != m_last_speed || force) - { - QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection, - Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0))); - m_last_speed = speed; - } + if (speed != m_last_speed || force) + { + QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection, + Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0))); + m_last_speed = speed; + } } } } @@ -1724,57 +1725,58 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor) emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor); } -namespace { -class QtHostProgressCallback final : public BaseProgressCallback +namespace { -public: - QtHostProgressCallback(); - ~QtHostProgressCallback() override; - - __fi const std::string& GetName() const { return m_name; } - - void PushState() override; - void PopState() override; - - bool IsCancelled() const override; - - void SetCancellable(bool cancellable) override; - void SetTitle(const char* title) override; - void SetStatusText(const char* text) override; - void SetProgressRange(u32 range) override; - void SetProgressValue(u32 value) override; - - void DisplayError(const char* message) override; - void DisplayWarning(const char* message) override; - void DisplayInformation(const char* message) override; - void DisplayDebugMessage(const char* message) override; - - void ModalError(const char* message) override; - bool ModalConfirmation(const char* message) override; - void ModalInformation(const char* message) override; - - void SetCancelled(); - -private: - struct SharedData + class QtHostProgressCallback final : public BaseProgressCallback { - QProgressDialog* dialog = nullptr; - QString init_title; - QString init_status_text; - std::atomic_bool cancelled{false}; - bool cancellable = true; - bool was_fullscreen = false; + public: + QtHostProgressCallback(); + ~QtHostProgressCallback() override; + + __fi const std::string& GetName() const { return m_name; } + + void PushState() override; + void PopState() override; + + bool IsCancelled() const override; + + void SetCancellable(bool cancellable) override; + void SetTitle(const char* title) override; + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + + void SetCancelled(); + + private: + struct SharedData + { + QProgressDialog* dialog = nullptr; + QString init_title; + QString init_status_text; + std::atomic_bool cancelled{false}; + bool cancellable = true; + bool was_fullscreen = false; + }; + + void EnsureHasData(); + static void EnsureDialogVisible(const std::shared_ptr& data); + void Redraw(bool force); + + std::string m_name; + std::shared_ptr m_data; + int m_last_progress_percent = -1; }; - - void EnsureHasData(); - static void EnsureDialogVisible(const std::shared_ptr& data); - void Redraw(bool force); - - std::string m_name; - std::shared_ptr m_data; - int m_last_progress_percent = -1; -}; -} +} // namespace QtHostProgressCallback::QtHostProgressCallback() : BaseProgressCallback() @@ -1829,7 +1831,7 @@ void QtHostProgressCallback::SetTitle(const char* title) void QtHostProgressCallback::SetStatusText(const char* text) { BaseProgressCallback::SetStatusText(text); - + EnsureHasData(); QtHost::RunOnUIThread([data = m_data, text = QString::fromUtf8(text)]() { if (data->dialog) @@ -2384,9 +2386,9 @@ int main(int argc, char* argv[]) if (s_start_fullscreen_ui) g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen); - if (s_boot_and_debug) + if (s_boot_and_debug || DebuggerWindow::shouldShowOnStartup()) { - DebugInterface::setPauseOnEntry(true); + DebugInterface::setPauseOnEntry(s_boot_and_debug); g_main_window->openDebugger(); } diff --git a/pcsx2-qt/QtUtils.cpp b/pcsx2-qt/QtUtils.cpp index 25ee99b67f..9c7a83db3c 100644 --- a/pcsx2-qt/QtUtils.cpp +++ b/pcsx2-qt/QtUtils.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,10 @@ #include #include +#ifdef Q_OS_LINUX +#include +#endif + #include #include #include @@ -351,4 +356,22 @@ namespace QtUtils } return csv; } + + bool IsLightTheme(const QPalette& palette) + { + return palette.text().color().lightnessF() < 0.5; + } + + bool IsCompositorManagerRunning() + { + if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING")) + return false; + +#ifdef Q_OS_LINUX + if (QX11Info::isPlatformX11() && !QX11Info::isCompositingManagerRunning()) + return false; +#endif + + return true; + } } // namespace QtUtils diff --git a/pcsx2-qt/QtUtils.h b/pcsx2-qt/QtUtils.h index c43e6f9260..fd590231d7 100644 --- a/pcsx2-qt/QtUtils.h +++ b/pcsx2-qt/QtUtils.h @@ -97,4 +97,9 @@ namespace QtUtils /// Converts an abstract item model to a CSV string. QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false); + + // Heuristic to check if the current theme is a light or dark theme. + bool IsLightTheme(const QPalette& palette); + + bool IsCompositorManagerRunning(); } // namespace QtUtils diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.cpp b/pcsx2-qt/Settings/DebugSettingsWidget.cpp index 617824d441..190b5af6bf 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.cpp +++ b/pcsx2-qt/Settings/DebugSettingsWidget.cpp @@ -3,6 +3,7 @@ #include "DebugSettingsWidget.h" +#include "DebugUserInterfaceSettingsWidget.h" #include "DebugAnalysisSettingsWidget.h" #include "QtUtils.h" #include "SettingWidgetBinder.h" @@ -20,6 +21,20 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* dialog, QWidget* parent m_ui.setupUi(this); + ////////////////////////////////////////////////////////////////////////// + // User Interface Settings + ////////////////////////////////////////////////////////////////////////// + if (!dialog->isPerGameSettings()) + { + m_user_interface_settings = new DebugUserInterfaceSettingsWidget(dialog); + m_ui.userInterfaceTabWidget->setLayout(new QVBoxLayout()); + m_ui.userInterfaceTabWidget->layout()->addWidget(m_user_interface_settings); + } + else + { + m_ui.debugTabs->removeTab(m_ui.debugTabs->indexOf(m_ui.userInterfaceTabWidget)); + } + ////////////////////////////////////////////////////////////////////////// // Analysis Settings ////////////////////////////////////////////////////////////////////////// @@ -141,9 +156,9 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* dialog, QWidget* parent connect(m_ui.chkEnable, &QCheckBox::checkStateChanged, this, &DebugSettingsWidget::onLoggingEnableChanged); onLoggingEnableChanged(); - #else - m_ui.debugTabs->removeTab(m_ui.debugTabs->indexOf(m_ui.traceLogTabWidget)); - #endif +#else + m_ui.debugTabs->removeTab(m_ui.debugTabs->indexOf(m_ui.traceLogTabWidget)); +#endif } DebugSettingsWidget::~DebugSettingsWidget() = default; diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.h b/pcsx2-qt/Settings/DebugSettingsWidget.h index c7ac560821..c2929f5d6e 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.h +++ b/pcsx2-qt/Settings/DebugSettingsWidget.h @@ -8,6 +8,7 @@ #include "ui_DebugSettingsWidget.h" class SettingsWindow; +class DebugUserInterfaceSettingsWidget; class DebugAnalysisSettingsWidget; class DebugSettingsWidget : public QWidget @@ -26,6 +27,8 @@ private Q_SLOTS: private: SettingsWindow* m_dialog; + + DebugUserInterfaceSettingsWidget* m_user_interface_settings; DebugAnalysisSettingsWidget* m_analysis_settings; Ui::DebugSettingsWidget m_ui; diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.ui b/pcsx2-qt/Settings/DebugSettingsWidget.ui index 1276ec33d4..7a1e8208c9 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.ui +++ b/pcsx2-qt/Settings/DebugSettingsWidget.ui @@ -6,7 +6,7 @@ 0 0 - 527 + 647 501 @@ -31,6 +31,11 @@ true + + + User Interface + + Analysis @@ -58,8 +63,8 @@ 0 0 - 523 - 464 + 645 + 469 @@ -196,18 +201,18 @@ - - - Save Alpha - - + + + Save Alpha + + - - - Save Info - - + + + Save Info + + diff --git a/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.cpp new file mode 100644 index 0000000000..a4d616e712 --- /dev/null +++ b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DebugUserInterfaceSettingsWidget.h" + +#include "SettingWidgetBinder.h" + +static const char* s_drop_indicators[] = { + QT_TRANSLATE_NOOP("DebugUserInterfaceSettingsWidget", "Classic"), + QT_TRANSLATE_NOOP("DebugUserInterfaceSettingsWidget", "Segmented"), + QT_TRANSLATE_NOOP("DebugUserInterfaceSettingsWidget", "Minimalistic"), + nullptr, +}; + +DebugUserInterfaceSettingsWidget::DebugUserInterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent) + : QWidget(parent) +{ + SettingsInterface* sif = dialog->getSettingsInterface(); + + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.showOnStartupCheckBox, "Debugger/UserInterface", "ShowOnStartup", false); + + dialog->registerWidgetHelp( + m_ui.showOnStartupCheckBox, tr("Show On Startup"), tr("Unchecked"), + tr("Open the debugger window automatically when PCSX2 starts.")); + + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.saveWindowGeometryCheckBox, "Debugger/UserInterface", "SaveWindowGeometry", true); + + dialog->registerWidgetHelp( + m_ui.saveWindowGeometryCheckBox, tr("Save Window Geometry"), tr("Checked"), + tr("Save the position and size of the debugger window when it is closed so that it can be restored later.")); + + SettingWidgetBinder::BindWidgetToEnumSetting( + sif, + m_ui.dropIndicatorCombo, + "Debugger/UserInterface", + "DropIndicatorStyle", + s_drop_indicators, + s_drop_indicators, + s_drop_indicators[0], + "DebugUserInterfaceSettingsWidget"); + + dialog->registerWidgetHelp( + m_ui.dropIndicatorCombo, tr("Drop Indicator Style"), tr("Classic"), + tr("Choose how the drop indicators that appear when you drag dock windows in the debugger are styled. " + "You will have to restart the debugger for this option to take effect.")); +} diff --git a/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.h b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.h new file mode 100644 index 0000000000..efb64da916 --- /dev/null +++ b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_DebugUserInterfaceSettingsWidget.h" + +class SettingsWindow; + +class DebugUserInterfaceSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + DebugUserInterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent = nullptr); + +private: + Ui::DebugUserInterfaceSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.ui b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.ui new file mode 100644 index 0000000000..c902a45030 --- /dev/null +++ b/pcsx2-qt/Settings/DebugUserInterfaceSettingsWidget.ui @@ -0,0 +1,106 @@ + + + DebugUserInterfaceSettingsWidget + + + + 0 + 0 + 500 + 750 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Debugger Window + + + + + + Save Window Geometry + + + + + + + + 0 + 0 + + + + Show On Startup + + + + + + + + + + + 0 + 0 + + + + Docking + + + + + + Drop Indicator Style + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 97be475830..228f5a77d4 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -101,6 +101,7 @@ + @@ -128,6 +129,7 @@ + @@ -209,6 +211,7 @@ + @@ -242,6 +245,7 @@ + @@ -294,6 +298,7 @@ + @@ -309,6 +314,7 @@ + @@ -476,6 +482,9 @@ Document + + Document + Document diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 706391a1c8..c62fffdba7 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -260,9 +260,15 @@ Settings + + Settings + moc + + moc + Settings @@ -320,6 +326,9 @@ Debugger\Docking + + Debugger\Docking + Debugger\Docking @@ -380,6 +389,9 @@ moc + + moc + moc @@ -592,6 +604,9 @@ Settings + + Settings + Settings @@ -649,6 +664,9 @@ Debugger\Docking + + Debugger\Docking + Debugger\Docking @@ -779,6 +797,9 @@ Settings + + Settings + Settings diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 3ce087f897..7075c530ea 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -1071,29 +1071,6 @@ struct Pcsx2Config static std::optional ParseSpeedHackName(const std::string_view name); }; - struct DebugOptions - { - BITFIELD32() - bool - ShowDebuggerOnStart : 1; - bool - AlignMemoryWindowStart : 1; - BITFIELD_END - - u8 FontWidth; - u8 FontHeight; - u32 WindowWidth; - u32 WindowHeight; - u32 MemoryViewBytesPerRow; - - - DebugOptions(); - void LoadSave(SettingsWrapper& wrap); - - bool operator==(const DebugOptions& right) const; - bool operator!=(const DebugOptions& right) const; - }; - // ------------------------------------------------------------------------ struct DebugAnalysisOptions { @@ -1304,7 +1281,6 @@ struct Pcsx2Config SpeedhackOptions Speedhacks; GamefixOptions Gamefixes; ProfilerOptions Profiler; - DebugOptions Debugger; DebugAnalysisOptions DebuggerAnalysis; EmulationSpeedOptions EmulationSpeed; SavestateOptions Savestate; diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 21b30eac04..c70f1e9480 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -1532,45 +1532,6 @@ void Pcsx2Config::GamefixOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(FullVU0SyncHack); } -Pcsx2Config::DebugOptions::DebugOptions() -{ - ShowDebuggerOnStart = false; - AlignMemoryWindowStart = true; - FontWidth = 8; - FontHeight = 12; - WindowWidth = 0; - WindowHeight = 0; - MemoryViewBytesPerRow = 16; -} - -void Pcsx2Config::DebugOptions::LoadSave(SettingsWrapper& wrap) -{ - SettingsWrapSection("EmuCore/Debugger"); - - SettingsWrapBitBool(ShowDebuggerOnStart); - SettingsWrapBitBool(AlignMemoryWindowStart); - SettingsWrapBitfield(FontWidth); - SettingsWrapBitfield(FontHeight); - SettingsWrapBitfield(WindowWidth); - SettingsWrapBitfield(WindowHeight); - SettingsWrapBitfield(MemoryViewBytesPerRow); -} - -bool Pcsx2Config::DebugOptions::operator!=(const DebugOptions& right) const -{ - return !this->operator==(right); -} - -bool Pcsx2Config::DebugOptions::operator==(const DebugOptions& right) const -{ - return OpEqu(bitset) && - OpEqu(FontWidth) && - OpEqu(FontHeight) && - OpEqu(WindowWidth) && - OpEqu(WindowHeight) && - OpEqu(MemoryViewBytesPerRow); -} - const char* Pcsx2Config::DebugAnalysisOptions::RunConditionNames[] = { "Always", "If Debugger Is Open", @@ -1980,7 +1941,6 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap) Profiler.LoadSave(wrap); Savestate.LoadSave(wrap); - Debugger.LoadSave(wrap); DebuggerAnalysis.LoadSave(wrap); Trace.LoadSave(wrap); @@ -2208,16 +2168,16 @@ bool EmuFolders::SetDataDirectory(Error* error) if (DataRoot.empty()) { #if defined(__linux__) - // special check if we're on appimage - // always make sure that DataRoot - // is adjacent next to the appimage - if (getenv("APPIMAGE")) - { - std::string_view appimage_path = Path::GetDirectory(getenv("APPIMAGE")); - DataRoot = Path::RealPath(Path::Combine(appimage_path, "PCSX2")); - } - else - DataRoot = Path::Combine(AppRoot, GetPortableModePath()); + // special check if we're on appimage + // always make sure that DataRoot + // is adjacent next to the appimage + if (getenv("APPIMAGE")) + { + std::string_view appimage_path = Path::GetDirectory(getenv("APPIMAGE")); + DataRoot = Path::RealPath(Path::Combine(appimage_path, "PCSX2")); + } + else + DataRoot = Path::Combine(AppRoot, GetPortableModePath()); #else DataRoot = Path::Combine(AppRoot, GetPortableModePath()); #endif