diff --git a/EXAMPLE.md b/EXAMPLE.md index 8a47f3f..0d987ce 100644 --- a/EXAMPLE.md +++ b/EXAMPLE.md @@ -118,7 +118,21 @@ Path: `~/.config/overzicht/settings.json` // Opacity applied to preview windows that belong to a different monitor than the current overview panel. "inactiveMonitorOpacity": 0.4, // Crop full-screen previews to fill the workspace tile. When false, the full preview remains visible with possible padding bars. - "cropToFill": false + "cropToFill": false, + // Keep one tooltip visible and animate it between hovered windows. Set false to use the original per-window popup behavior. + "smoothTooltipMovement": true, + "tooltipAnimations": { + "expandCollapse": { + // Animate tooltip expansion when shown and collapse when hidden. + "enable": true + }, + "fade": { + // Fade tooltips in and out. + "enable": true, + // Duration of the fade animation in milliseconds. + "duration": 200 + } + } }, "hacks": { // Small delay used before focus grabs and some window-position recalculations to avoid timing issues. diff --git a/README.md b/README.md index 52a79f5..2b9891e 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,9 @@ Useful settings: - `appearance.rounding.screenRounding` and `appearance.rounding.windowRounding` control overview corner radius. Set them to `0` for a square UI. - `windowPreview.showIcons` controls centered app icons on previews. - `windowPreview.cropToFill` controls whether full-screen previews crop into the tile. +- `windowPreview.smoothTooltipMovement` animates the bordered tooltip between hovered windows; set it to `false` for the original per-window popups. +- `windowPreview.tooltipAnimations.expandCollapse.enable` controls tooltip expansion and collapse. +- `windowPreview.tooltipAnimations.fade` controls tooltip fading with `enable` and `duration`. ## Credits diff --git a/common/Config.qml b/common/Config.qml index ff6629f..b7ae280 100644 --- a/common/Config.qml +++ b/common/Config.qml @@ -132,6 +132,16 @@ Singleton { property real xwaylandIndicatorToIconRatio: root.optionValue(["windowPreview", "xwaylandIndicatorToIconRatio"], 0.35) property real inactiveMonitorOpacity: root.optionValue(["windowPreview", "inactiveMonitorOpacity"], 0.4) property bool cropToFill: root.optionValue(["windowPreview", "cropToFill"], false) + property bool smoothTooltipMovement: root.optionValue(["windowPreview", "smoothTooltipMovement"], true) + property QtObject tooltipAnimations: QtObject { + property QtObject expandCollapse: QtObject { + property bool enable: root.optionValue(["windowPreview", "tooltipAnimations", "expandCollapse", "enable"], true) + } + property QtObject fade: QtObject { + property bool enable: root.optionValue(["windowPreview", "tooltipAnimations", "fade", "enable"], true) + property int duration: root.optionValue(["windowPreview", "tooltipAnimations", "fade", "duration"], 200) + } + } } property QtObject hacks: QtObject { diff --git a/common/widgets/StyledToolTip.qml b/common/widgets/StyledToolTip.qml index 4214675..da9ed4e 100644 --- a/common/widgets/StyledToolTip.qml +++ b/common/widgets/StyledToolTip.qml @@ -6,6 +6,9 @@ ToolTip { id: root property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false + property bool expandCollapseAnimationEnabled: true + property bool fadeAnimationEnabled: true + property int fadeAnimationDuration: Appearance?.animation.elementMoveFast.duration ?? 200 readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition verticalPadding: 5 horizontalPadding: 10 @@ -17,6 +20,9 @@ ToolTip { id: contentItem text: root.text shown: root.internalVisibleCondition + expandCollapseAnimationEnabled: root.expandCollapseAnimationEnabled + fadeAnimationEnabled: root.fadeAnimationEnabled + fadeAnimationDuration: root.fadeAnimationDuration horizontalPadding: root.horizontalPadding verticalPadding: root.verticalPadding } diff --git a/common/widgets/StyledToolTipContent.qml b/common/widgets/StyledToolTipContent.qml index 3a2c711..d3d6b23 100644 --- a/common/widgets/StyledToolTipContent.qml +++ b/common/widgets/StyledToolTipContent.qml @@ -6,6 +6,10 @@ Item { id: root required property string text property bool shown: false + property bool collapseWhenHidden: true + property bool expandCollapseAnimationEnabled: true + property bool fadeAnimationEnabled: true + property int fadeAnimationDuration: Appearance?.animation.elementMoveFast.duration ?? 200 property real horizontalPadding: 10 property real verticalPadding: 5 implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding @@ -21,19 +25,28 @@ Item { } color: Appearance?.colors.colTooltip radius: Appearance?.rounding.verysmall + border.width: 1 + border.color: ColorUtils.transparentize(Appearance?.palette.outline, 0.7) opacity: shown ? 1 : 0 - implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0 - implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0 + implicitWidth: shown || !root.collapseWhenHidden ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0 + implicitHeight: shown || !root.collapseWhenHidden ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0 clip: true Behavior on implicitWidth { + enabled: root.expandCollapseAnimationEnabled animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitHeight { + enabled: root.expandCollapseAnimationEnabled animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on opacity { - animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + enabled: root.fadeAnimationEnabled + NumberAnimation { + duration: Math.max(0, root.fadeAnimationDuration) + easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.OutCubic + easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [] + } } StyledText { diff --git a/modules/overview/OverviewWidget.qml b/modules/overview/OverviewWidget.qml index 114af26..1cc0a0c 100644 --- a/modules/overview/OverviewWidget.qml +++ b/modules/overview/OverviewWidget.qml @@ -46,7 +46,34 @@ Item { property int draggingFromWorkspace: -1 property int draggingTargetWorkspace: -1 property bool hideEmptyRows: Config.options.overview.hideEmptyRows + property bool smoothTooltipMovement: Config.options.windowPreview.smoothTooltipMovement + property bool tooltipExpandCollapseAnimationEnabled: Config.options.windowPreview.tooltipAnimations.expandCollapse.enable + property bool tooltipFadeAnimationEnabled: Config.options.windowPreview.tooltipAnimations.fade.enable + property int tooltipFadeAnimationDuration: Math.max(0, Config.options.windowPreview.tooltipAnimations.fade.duration) readonly property bool hasEmptyWorkspaceWallpaper: `${emptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0 + onSmoothTooltipMovementChanged: { + tooltipHideTimer.stop(); + sharedWindowTooltip.targetWindow = null; + } + + function windowTooltipText(window) { + const data = window?.windowData; + return `${data?.title ?? "Unknown"}\n[${data?.class ?? "unknown"}] ${data?.xwayland ? "[XWayland] " : ""}`; + } + + function showWindowTooltip(window) { + if (!root.smoothTooltipMovement) + return; + + tooltipHideTimer.stop(); + sharedWindowTooltip.tooltipText = root.windowTooltipText(window); + sharedWindowTooltip.targetWindow = window; + } + + function hideWindowTooltip(window) { + if (root.smoothTooltipMovement && sharedWindowTooltip.targetWindow === window) + tooltipHideTimer.restart(); + } function getWorkspaceRow(workspaceId) { if (!Number.isFinite(workspaceId)) @@ -364,8 +391,14 @@ Item { id: dragArea anchors.fill: parent hoverEnabled: true - onEntered: hovered = true - onExited: hovered = false + onEntered: { + hovered = true; + root.showWindowTooltip(window); + } + onExited: { + hovered = false; + root.hideWindowTooltip(window); + } acceptedButtons: Qt.LeftButton | Qt.MiddleButton drag.target: parent onPressed: mouse => { @@ -431,8 +464,11 @@ Item { StyledToolTip { extraVisibleCondition: false - alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active - text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}` + alternativeVisibleCondition: !root.smoothTooltipMovement && dragArea.containsMouse && !window.Drag.active + text: root.windowTooltipText(window) + expandCollapseAnimationEnabled: root.tooltipExpandCollapseAnimationEnabled + fadeAnimationEnabled: root.tooltipFadeAnimationEnabled + fadeAnimationDuration: root.tooltipFadeAnimationDuration } } } @@ -460,4 +496,54 @@ Item { } } } + + Timer { + id: tooltipHideTimer + interval: Appearance.animation.elementMoveFast.duration + repeat: false + onTriggered: sharedWindowTooltip.targetWindow = null + } + + StyledToolTipContent { + id: sharedWindowTooltip + property var targetWindow: null + property bool hasPosition: false + property string tooltipText: "" + property real tooltipX: 0 + property real tooltipY: 0 + readonly property real edgeMargin: Appearance.sizes.elevationMargin + + x: tooltipX + y: tooltipY + z: root.windowDraggingZ + 1 + width: implicitWidth + height: implicitHeight + text: tooltipText + shown: root.smoothTooltipMovement && targetWindow !== null && !targetWindow.Drag.active + collapseWhenHidden: true + expandCollapseAnimationEnabled: root.tooltipExpandCollapseAnimationEnabled + fadeAnimationEnabled: root.tooltipFadeAnimationEnabled + fadeAnimationDuration: root.tooltipFadeAnimationDuration + + onTargetWindowChanged: { + if (targetWindow) { + const targetTop = targetWindow.mapToItem(root, targetWindow.width / 2, 0); + tooltipX = Math.max(edgeMargin, Math.min(root.width - width - edgeMargin, targetTop.x - width / 2)); + tooltipY = targetTop.y - height - edgeMargin; + if (!hasPosition) + Qt.callLater(() => sharedWindowTooltip.hasPosition = true); + } else { + hasPosition = false; + } + } + + Behavior on x { + enabled: sharedWindowTooltip.hasPosition + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on y { + enabled: sharedWindowTooltip.hasPosition + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } }