diff --git a/panels/dock/dockpanel.cpp b/panels/dock/dockpanel.cpp index 0fb95980a..0651cb5ad 100644 --- a/panels/dock/dockpanel.cpp +++ b/panels/dock/dockpanel.cpp @@ -17,8 +17,10 @@ #include "dockdaemonadaptor.h" #include +#include #include #include +#include #include #include #include @@ -45,6 +47,10 @@ DockPanel::DockPanel(QObject *parent) , m_contextDragging(false) , m_isResizing(false) { + connect(this, &DockPanel::fashionModeChanged, this, &DockPanel::syncSpotlightTracking); + connect(this, &DockPanel::hideStateChanged, this, &DockPanel::syncSpotlightTracking); + connect(this, &DockPanel::rootObjectChanged, this, &DockPanel::syncSpotlightTracking); + // The org.deepin.dde.Dock1 service name is used as the readiness flag of the // Type=dbus systemd unit (dde-shell@DDE.service). It must only be acquired // after the QML DockCompositor is created, so that tray plugin loader @@ -546,13 +552,62 @@ qreal DockPanel::devicePixelRatio() const bool DockPanel::eventFilter(QObject *watched, QEvent *event) { - if (watched == window() && event->type() == QEvent::DevicePixelRatioChange) { + if (watched != window()) + return false; + + if (event->type() == QEvent::DevicePixelRatioChange) { Q_EMIT devicePixelRatioChanged(window()->devicePixelRatio()); + } else if (m_spotlightTracking) { + // Observe the window before delivery to Qt Quick controls. No + // per-item handlers, coordinate reports or periodic cursor queries. + switch (event->type()) { + case QEvent::Enter: + setCursorPosition(static_cast(event)->position()); + setContainsMouse(true); + break; + case QEvent::MouseMove: { + const auto position = static_cast(event)->position(); + setCursorPosition(position); + // A pressed control may keep its grab after the pointer leaves. + setContainsMouse(QRectF(QPointF(), window()->size()).contains(position)); + break; + } + case QEvent::Leave: + case QEvent::Hide: + setContainsMouse(false); + break; + default: + break; + } } return false; } +void DockPanel::syncSpotlightTracking() +{ + // Cache the mode so the hot event path never needs to query settings. + m_spotlightTracking = fashionMode() && hideState() == Show; + if (!m_spotlightTracking) + setContainsMouse(false); +} + +void DockPanel::setContainsMouse(bool containsMouse) +{ + if (m_containsMouse == containsMouse) + return; + m_containsMouse = containsMouse; + Q_EMIT containsMouseChanged(containsMouse); +} + +void DockPanel::setCursorPosition(const QPointF &cursorPosition) +{ + if (m_cursorPosition == cursorPosition) + return; + m_cursorPosition = cursorPosition; + Q_EMIT cursorPositionChanged(cursorPosition); +} + bool DockPanel::contextDragging() const { return m_contextDragging; diff --git a/panels/dock/dockpanel.h b/panels/dock/dockpanel.h index a7551639e..9b753253c 100644 --- a/panels/dock/dockpanel.h +++ b/panels/dock/dockpanel.h @@ -10,6 +10,7 @@ #include #include +#include namespace dock { class DockHelper; @@ -44,6 +45,8 @@ class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext Q_PROPERTY(bool debugMode READ debugMode FINAL CONSTANT) Q_PROPERTY(bool contextDragging READ contextDragging WRITE setContextDragging NOTIFY contextDraggingChanged FINAL) + Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged FINAL) + Q_PROPERTY(QPointF cursorPosition READ cursorPosition NOTIFY cursorPositionChanged FINAL) public: explicit DockPanel(QObject *parent = nullptr); @@ -119,6 +122,9 @@ class DockPanel : public DS_NAMESPACE::DPanel, public QDBusContext bool contextDragging() const; void setContextDragging(bool newContextDragging); + bool containsMouse() const { return m_containsMouse; } + QPointF cursorPosition() const { return m_cursorPosition; } + bool isResizing() const; void setIsResizing(bool resizing); @@ -129,6 +135,7 @@ private Q_SLOTS: void onWindowGeometryChanged(); void launcherVisibleChanged(bool visible); void updateDockScreen(); + void syncSpotlightTracking(); Q_SIGNALS: void geometryChanged(QRect geometry); @@ -156,12 +163,20 @@ private Q_SLOTS: void fashionModeEnabledChanged(bool enabled); void contextDraggingChanged(); + void containsMouseChanged(bool containsMouse); + void cursorPositionChanged(const QPointF &cursorPosition); void isResizingChanged(bool isResizing); // Emitted when async XEmbed window move completes void xembedWindowMoveResult(uint32_t wid, bool success); private: + void setContainsMouse(bool containsMouse); + void setCursorPosition(const QPointF &cursorPosition); + + bool m_spotlightTracking = false; + bool m_containsMouse = false; + QPointF m_cursorPosition; ColorTheme m_theme; HideState m_hideState; DockHelper* m_helper; diff --git a/panels/dock/package/DockSpotlight.qml b/panels/dock/package/DockSpotlight.qml new file mode 100644 index 000000000..c87317763 --- /dev/null +++ b/panels/dock/package/DockSpotlight.qml @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick + +Item { + id: spotlight + property bool containsMouse: false + property point cursorPosition: Qt.point(0, 0) + property real itemSize: 48 + property bool darkTheme: false + readonly property real baseRadius: Math.max(0, Math.min(itemSize * 3.0, height * 3.0, width * 0.18)) + readonly property real coreRadius: baseRadius * 0.35 + + clip: true + enabled: false + visible: opacity > 0 + opacity: containsMouse ? 1 : 0 + + Behavior on opacity { + NumberAnimation { duration: 90; easing.type: Easing.OutQuad } + } + + // Move a single transform instead of evaluating both images' coordinates + // on every pointer event. Both layers share the same cached 256px texture. + Item { + x: Math.max(0, Math.min(spotlight.cursorPosition.x, spotlight.width)) + y: Math.max(0, Math.min(spotlight.cursorPosition.y, spotlight.height)) + + Image { + objectName: "spotlightImage" + width: spotlight.baseRadius * 2 + height: spotlight.baseRadius * 2.6 + x: -width / 2 + y: -height / 2 + source: "SpotlightGradient.png" + fillMode: Image.Stretch + smooth: true + opacity: spotlight.darkTheme ? 0.14 : 0.2 + } + + Image { + objectName: "spotlightCoreImage" + width: spotlight.coreRadius * 2 + height: width + x: -width / 2 + y: -height / 2 + source: "SpotlightGradient.png" + fillMode: Image.Stretch + smooth: true + opacity: spotlight.darkTheme ? 0.23 : 0.32 + } + } +} diff --git a/panels/dock/package/SpotlightGradient.png b/panels/dock/package/SpotlightGradient.png new file mode 100644 index 000000000..07bf94ea8 Binary files /dev/null and b/panels/dock/package/SpotlightGradient.png differ diff --git a/panels/dock/package/main.qml b/panels/dock/package/main.qml index 95afad169..56f5227af 100644 --- a/panels/dock/package/main.qml +++ b/panels/dock/package/main.qml @@ -499,6 +499,17 @@ Window { } } + Loader { + anchors.fill: parent + active: dock.fashionDock.enabled && Panel.hideState === Dock.Show + sourceComponent: DockSpotlight { + containsMouse: Panel.containsMouse + cursorPosition: dockContainer.mapFromItem(null, Panel.cursorPosition) + itemSize: dock.dockItemMaxSize + darkTheme: Panel.colorTheme === Dock.Dark + } + } + TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton gesturePolicy: TapHandler.WithinBounds