diff --git a/EXAMPLE.md b/EXAMPLE.md --- a/EXAMPLE.md +++ b/EXAMPLE.md @@ -108,7 +108,9 @@ // Reserved XWayland indicator ratio; exposed in config but not currently rendered. "xwaylandIndicatorToIconRatio": 0.35, // Opacity applied to preview windows that belong to a different monitor than the current overview panel. - "inactiveMonitorOpacity": 0.4 + "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 }, "hacks": { // Small delay used before focus grabs and some window-position recalculations to avoid timing issues. diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ Useful `windowPreview` toggles include: - `showIcons` to show or hide application icons centered over window previews. Defaults to `true`. +- `cropToFill` to crop full-screen previews to fill the workspace tile when `true`; defaults to `false` so the full preview remains visible with possible padding bars. > [!TIP] > `previewMode` accepts `live` and event-driven values such as `event` or `snapshot`. For the full schema and examples, use [`EXAMPLE.md`](./EXAMPLE.md). diff --git a/common/Config.qml b/common/Config.qml --- a/common/Config.qml +++ b/common/Config.qml @@ -120,6 +120,7 @@ property real iconToWindowRatioCompact: root.optionValue(["windowPreview", "iconToWindowRatioCompact"], 0.45) 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 QtObject hacks: QtObject { diff --git a/modules/overview/OverviewWidget.qml b/modules/overview/OverviewWidget.qml --- a/modules/overview/OverviewWidget.qml +++ b/modules/overview/OverviewWidget.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Wayland @@ -28,8 +29,8 @@ property real scale: Config.options.overview.scale property color activeBorderColor: Appearance.colors.colSecondary - property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? ((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) : ((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) - property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? ((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) : ((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) + property real workspaceImplicitWidth: Math.round((monitorData?.transform % 2 === 1) ? ((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) : ((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale)) + property real workspaceImplicitHeight: Math.round((monitorData?.transform % 2 === 1) ? ((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) : ((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale)) property real workspaceNumberMargin: 80 property real workspaceNumberSize: Config.options.overview.workspaceNumberBaseSize * monitor.scale @@ -184,6 +185,7 @@ border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent" Image { + id: workspaceWallpaper visible: workspace.showWallpaper anchors.fill: parent source: root.wallpaperSource(root.emptyWorkspaceWallpaperPath) @@ -192,6 +194,26 @@ cache: true smooth: true mipmap: true + layer.enabled: workspace.showWallpaper + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: workspaceWallpaperMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + } + } + + Item { + id: workspaceWallpaperMask + anchors.fill: parent + visible: false + layer.enabled: true + layer.smooth: true + Rectangle { + anchors.fill: parent + radius: workspace.radius + } } Rectangle { diff --git a/modules/overview/OverviewWindow.qml b/modules/overview/OverviewWindow.qml --- a/modules/overview/OverviewWindow.qml +++ b/modules/overview/OverviewWindow.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Layouts +import QtQuick.Effects import Quickshell import Quickshell.Wayland import Quickshell.Hyprland @@ -30,6 +31,7 @@ property var iconToWindowRatio: Config.options.windowPreview.iconToWindowRatio property var xwaylandIndicatorToIconRatio: Config.options.windowPreview.xwaylandIndicatorToIconRatio property var iconToWindowRatioCompact: Config.options.windowPreview.iconToWindowRatioCompact + property bool cropToFill: Config.options.windowPreview.cropToFill property bool previewsEnabled: Config.options.overview.previewsEnabled property bool includeInactiveMonitorPreviews: Config.options.overview.includeInactiveMonitorPreviews property int previewRecaptureDelayMs: Config.options.overview.previewRecaptureDelayMs @@ -67,6 +69,13 @@ width: Math.min(targetWindowWidth, availableWorkspaceWidth) height: Math.min(targetWindowHeight, availableWorkspaceHeight) opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : Config.options.windowPreview.inactiveMonitorOpacity + visible: { + const thisWsId = windowData?.workspace?.id; + const isFullscreen = (windowData?.fullscreen ?? 0) > 0; + if (isFullscreen || thisWsId === undefined) + return true; + return !HyprlandData.windowList.some(w => w.workspace?.id === thisWsId && (w.fullscreen ?? 0) > 0); + } clip: true Component.onCompleted: Qt.callLater(() => root.initialized = true) @@ -88,19 +97,44 @@ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } + // Opaque background for windows on the active monitor. + // The simplest solution for making those windows fully opaque and not interacting with actual + // windows behind the overview, e.g., applying blur to them. + Rectangle { + visible: (root.windowData?.monitor ?? -1) === root.widgetMonitorId + anchors.fill: parent + radius: Appearance.rounding.windowRounding * root.scale + color: Appearance.colors.colLayer1 + } + ScreencopyView { id: windowPreview - anchors.fill: parent + readonly property real srcAspect: { + const w = root.windowData?.size?.[0] ?? 0; + const h = root.windowData?.size?.[1] ?? 0; + return (w > 0 && h > 0) ? (w / h) : 1; + } + anchors.centerIn: parent + width: root.cropToFill ? Math.max(parent.width, parent.height * srcAspect) : Math.min(parent.width, parent.height * srcAspect) + height: root.cropToFill ? Math.max(parent.height, parent.width / srcAspect) : Math.min(parent.height, parent.width / srcAspect) captureSource: shouldCapturePreview ? root.toplevel : null live: livePreviewEnabled - - Rectangle { - anchors.fill: parent - radius: 0 - color: root.pressed ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Active, Math.min(1, root.windowOverlayOpacity + 0.30)) : root.hovered ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Hover, Math.min(1, root.windowOverlayOpacity + 0.20)) : ColorUtils.transparentize(Appearance.colors.colLayer2) - border.color: ColorUtils.transparentize(Appearance.palette.outline, 0.7) - border.width: 1 + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: previewMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 } + } + + Rectangle { + anchors.fill: parent + radius: Appearance.rounding.windowRounding * root.scale + color: pressed ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Active, Math.min(1, root.windowOverlayOpacity + 0.30)) : hovered ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Hover, Math.min(1, root.windowOverlayOpacity + 0.20)) : ColorUtils.transparentize(Appearance.colors.colLayer2) + border.color: ColorUtils.transparentize(Appearance.palette.outline, 0.7) + border.width: 1 ColumnLayout { anchors.verticalCenter: parent.verticalCenter @@ -127,6 +161,22 @@ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } + } + } + + Item { + id: previewMask + width: windowPreview.width + height: windowPreview.height + anchors.centerIn: parent + visible: false + layer.enabled: true + layer.smooth: true + Rectangle { + anchors.centerIn: parent + width: root.width + height: root.height + radius: Appearance.rounding.windowRounding * root.scale } }