diff --git a/quickshell/Common/ConnectedModeState.qml b/quickshell/Common/ConnectedModeState.qml index e62e3e4a..c5b9a6e3 100644 --- a/quickshell/Common/ConnectedModeState.qml +++ b/quickshell/Common/ConnectedModeState.qml @@ -74,10 +74,6 @@ Singleton { }, descriptor); } - function legacySurfaceState(screenName, kind) { - return SurfaceDescriptor.toLegacyState(surfaceDescriptor(screenName, kind)); - } - function hasSurfaceDescriptor(screenName, kind, ownerId) { const descriptor = surfaceDescriptor(screenName, kind); return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId); @@ -124,20 +120,6 @@ Singleton { return true; } - function _setSurfaceAnimation(screenName, kind, ownerId, x, y) { - const current = surfaceDescriptor(screenName, kind); - if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId)) - return false; - return true; - } - - function _setSurfaceBody(screenName, kind, ownerId, x, y, width, height) { - const current = surfaceDescriptor(screenName, kind); - if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId)) - return false; - return true; - } - readonly property var emptyDockState: ({ "reveal": false, "barSide": "bottom", @@ -289,7 +271,6 @@ Singleton { if (!isNaN(nextY) && popoutAnimY !== nextY) popoutAnimY = nextY; } - _setSurfaceAnimation(popoutScreen, "popout", claimId, animX, animY); return true; } @@ -316,7 +297,6 @@ Singleton { if (!isNaN(nextH) && popoutBodyH !== nextH) popoutBodyH = nextH; } - _setSurfaceBody(popoutScreen, "popout", claimId, bodyX, bodyY, bodyW, bodyH); return true; } @@ -352,8 +332,8 @@ Singleton { "phase": normalized.reveal ? (state.phase || "open") : "hidden" }); const previous = dockStates[screenName] || emptyDockState; - const legacyChanged = !_sameDockState(dockStates[screenName], normalized); - if (legacyChanged) { + const stateChanged = !_sameDockState(dockStates[screenName], normalized); + if (stateChanged) { const next = _cloneDict(dockStates); next[screenName] = normalized; dockStates = next; @@ -397,7 +377,6 @@ Singleton { "y": numY }; dockSlides = next; - _setSurfaceAnimation(screenName, "dock", "dock:" + screenName, numX, numY); return true; } @@ -451,8 +430,8 @@ Singleton { "phase": normalized.visible ? (state.phase || "open") : "hidden" }); const previous = notificationStates[screenName] || emptyNotificationState; - const legacyChanged = !_sameNotificationState(notificationStates[screenName], normalized); - if (legacyChanged) { + const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized); + if (stateChanged) { const next = _cloneDict(notificationStates); next[screenName] = normalized; notificationStates = next; @@ -573,10 +552,6 @@ Singleton { return updateModalState(screenName, state, ownerId); } - function setModalState(screenName, state) { - return updateModalState(screenName, state, null); - } - function clearModalState(screenName, ownerId) { if (!screenName) return false; @@ -617,7 +592,6 @@ Singleton { "animY": nay }); modalStates = next; - _setSurfaceAnimation(screenName, "modal", ownerId, animX, animY); return true; } @@ -641,7 +615,6 @@ Singleton { "bodyH": nh }); modalStates = next; - _setSurfaceBody(screenName, "modal", ownerId, bodyX, bodyY, bodyW, bodyH); return true; } diff --git a/quickshell/Common/ConnectedSurfaceDescriptor.js b/quickshell/Common/ConnectedSurfaceDescriptor.js index 7a739789..203e1eb0 100644 --- a/quickshell/Common/ConnectedSurfaceDescriptor.js +++ b/quickshell/Common/ConnectedSurfaceDescriptor.js @@ -157,23 +157,3 @@ function same(a, b, threshold) { && a.omitEndConnector === b.omitEndConnector && a.dockRetractSide === b.dockRetractSide; } - -function toLegacyState(descriptor) { - var d = normalize(descriptor); - return { - "visible": d.visible, - "reveal": d.visible, - "barSide": d.barSide, - "bodyX": d.bodyRect.x, - "bodyY": d.bodyRect.y, - "bodyW": d.bodyRect.width, - "bodyH": d.bodyRect.height, - "animX": d.animationOffset.x, - "animY": d.animationOffset.y, - "slideX": d.animationOffset.x, - "slideY": d.animationOffset.y, - "screen": d.screenName, - "omitStartConnector": d.omitStartConnector, - "omitEndConnector": d.omitEndConnector - }; -} diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index 393e77fc..ef469f63 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -58,6 +58,7 @@ DankModal { readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable visible: false + keepContentLoaded: true modalWidth: ClipboardConstants.modalWidth modalHeight: ClipboardConstants.modalHeight backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) diff --git a/quickshell/Modals/Common/DankModal.qml b/quickshell/Modals/Common/DankModal.qml index 30865cbc..c5106143 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -56,21 +56,13 @@ Item { // One focus grab for every modal; on Hyprland this is what delivers // keyboard focus to the OnDemand surface, identically in both modes. - // The clickCatcher is whitelisted so an outside click is delivered to - // it (closing the modal) instead of being consumed clearing the grab. + // The full-surface content window receives outside clicks itself, so an + // outside click closes the modal instead of being consumed by the grab. HyprlandFocusGrab { - windows: { - const list = []; - if (root.contentWindow) - list.push(root.contentWindow); - if (root.clickCatcher) - list.push(root.clickCatcher); - return list; - } + windows: root.contentWindow ? [root.contentWindow] : [] active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus) } readonly property var contentWindow: impl.item ? impl.item.contentWindow : null - readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920 readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080 diff --git a/quickshell/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml index 07feb34f..25f2a208 100644 --- a/quickshell/Modals/Common/DankModalConnected.qml +++ b/quickshell/Modals/Common/DankModalConnected.qml @@ -87,10 +87,8 @@ Item { property real frozenMotionOffsetX: 0 property real frozenMotionOffsetY: 0 readonly property alias contentWindow: contentWindow - readonly property alias clickCatcher: clickCatcher readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab readonly property bool useBackground: false - readonly property bool useSingleWindow: CompositorService.isHyprland signal opened signal dialogClosed @@ -244,22 +242,16 @@ Item { const focusedScreen = CompositorService.getFocusedScreen(); if (focusedScreen) { contentWindow.screen = focusedScreen; - if (!useSingleWindow) - clickCatcher.screen = focusedScreen; } ModalManager.openModal(modalHandle); if (Theme.isDirectionalEffect || root.useBackground) { - if (!useSingleWindow) - clickCatcher.visible = true; contentWindow.visible = true; } Qt.callLater(() => { animationsEnabled = true; shouldBeVisible = true; - if (!useSingleWindow && !clickCatcher.visible) - clickCatcher.visible = true; if (!contentWindow.visible) contentWindow.visible = true; opened(); @@ -286,8 +278,6 @@ Item { ModalManager.closeModal(modalHandle); closeTimer.stop(); contentWindow.visible = false; - if (!useSingleWindow) - clickCatcher.visible = false; dialogClosed(); Qt.callLater(() => animationsEnabled = true); } @@ -326,8 +316,6 @@ Item { const newScreen = CompositorService.getFocusedScreen(); if (newScreen) { contentWindow.screen = newScreen; - if (!useSingleWindow) - clickCatcher.screen = newScreen; } } } @@ -339,29 +327,12 @@ Item { if (shouldBeVisible) return; contentWindow.visible = false; - if (!useSingleWindow) - clickCatcher.visible = false; dialogClosed(); } } - // shadowRenderPadding is zeroed when frame owns the chrome - // Wayland then clips any content translating past readonly property var shadowLevel: Theme.elevationLevel3 readonly property real shadowFallbackOffset: 6 - readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0 - readonly property real shadowMotionPadding: { - if (frameOwnsConnectedChrome) - return 0; - if (animationType === "slide") - return 30; - if (Theme.isDirectionalEffect) - return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9); - if (Theme.isDepthEffect) - return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35); - return Math.max(0, animationOffset); - } - readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr) readonly property real alignedWidth: Theme.px(modalWidth, dpr) readonly property real alignedHeight: Theme.px(modalHeight, dpr) @@ -434,57 +405,6 @@ Item { } })(), dpr) - PanelWindow { - id: clickCatcher - visible: false - color: "transparent" - - WlrLayershell.namespace: root.layerNamespace + ":clickcatcher" - WlrLayershell.layer: WlrLayershell.Top - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - anchors { - top: true - left: true - right: true - bottom: true - } - - mask: Region { - item: Rectangle { - x: root.alignedX - y: root.alignedY - width: root.alignedWidth - height: root.alignedHeight - } - intersection: Intersection.Xor - } - - MouseArea { - anchors.fill: parent - enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible - onClicked: root.backgroundClicked() - } - - Rectangle { - anchors.fill: parent - z: -1 - color: "black" - opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 - visible: opacity > 0 - - Behavior on opacity { - enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect) - NumberAnimation { - duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - } - } - PanelWindow { id: contentWindow visible: false @@ -494,8 +414,8 @@ Item { targetWindow: contentWindow blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome readonly property real s: Math.min(1, modalContainer.scaleValue) - blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr) - blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr) + blurX: connectedReveal.x + modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr) + blurY: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr) blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0 blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0 blurRadius: root.effectiveCornerRadius @@ -514,23 +434,10 @@ Item { anchors { left: true top: true - right: root.useSingleWindow - bottom: root.useSingleWindow + right: true + bottom: true } - readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr)) - readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr)) - - WlrLayershell.margins { - left: actualMarginLeft - top: actualMarginTop - right: 0 - bottom: 0 - } - - implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2) - implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2) - onVisibleChanged: { if (visible) return; @@ -542,7 +449,7 @@ Item { MouseArea { anchors.fill: parent - enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible + enabled: root.closeOnBackgroundClick && root.shouldBeVisible z: -2 onClicked: root.backgroundClicked() } @@ -551,7 +458,7 @@ Item { anchors.fill: parent z: -1 color: "black" - opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 + opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 visible: opacity > 0 Behavior on opacity { @@ -564,250 +471,263 @@ Item { } } + // Reveal frame: when the frame owns the connected chrome, the content + // must only become visible inside the modal's final footprint so it + // emerges in step with the chrome growing from the bar edge (the old + // two-window layout got this for free from the window boundary). Item { - id: modalContainer - x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr) - y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr) - + id: connectedReveal + x: root.alignedX + y: root.alignedY width: root.alignedWidth height: root.alignedHeight - - MouseArea { - anchors.fill: parent - enabled: root.useSingleWindow && root.shouldBeVisible - hoverEnabled: false - acceptedButtons: Qt.AllButtons - onPressed: mouse.accepted = true - onClicked: mouse.accepted = true - z: -1 - } - - readonly property bool slide: root.animationType === "slide" - readonly property bool directionalEffect: Theme.isDirectionalEffect - readonly property bool depthEffect: Theme.isDepthEffect - readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8) - readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36) - readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5 - readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5 - readonly property real customDistLeft: customAnchorX - readonly property real customDistRight: root.screenWidth - customAnchorX - readonly property real customDistTop: customAnchorY - readonly property real customDistBottom: root.screenHeight - customAnchorY - // Connected emergence: travel from the resolved bar edge, matching DankPopout cadence. - readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL) - readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL) - readonly property real offsetX: { - if (root.frameOwnsConnectedChrome) { - switch (root.resolvedConnectedBarSide) { - case "left": - return -connectedEmergenceTravelX; - case "right": - return connectedEmergenceTravelX; - } - return 0; - } - if (slide && !directionalEffect && !depthEffect) - return 15; - if (directionalEffect) { - switch (root.positioning) { - case "top-right": - return 0; - case "custom": - if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom) - return -directionalTravel; - if (customDistRight <= customDistTop && customDistRight <= customDistBottom) - return directionalTravel; - return 0; - default: - return 0; - } - } - if (depthEffect) { - switch (root.positioning) { - case "top-right": - return 0; - case "custom": - if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom) - return -depthTravel; - if (customDistRight <= customDistTop && customDistRight <= customDistBottom) - return depthTravel; - return 0; - default: - return 0; - } - } - return 0; - } - readonly property real offsetY: { - if (root.frameOwnsConnectedChrome) { - switch (root.resolvedConnectedBarSide) { - case "top": - return -connectedEmergenceTravelY; - case "bottom": - return connectedEmergenceTravelY; - } - return 0; - } - if (slide && !directionalEffect && !depthEffect) - return -30; - if (directionalEffect) { - switch (root.positioning) { - case "top-right": - return -Math.max(directionalTravel * 0.65, 96); - case "custom": - if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight) - return -directionalTravel; - if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight) - return directionalTravel; - return 0; - default: - // Default to sliding down from top when centered - return -Math.max(directionalTravel, root.screenHeight * 0.24); - } - } - if (depthEffect) { - switch (root.positioning) { - case "top-right": - return -depthTravel * 0.75; - case "custom": - if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight) - return -depthTravel; - if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight) - return depthTravel; - return depthTravel * 0.45; - default: - return -depthTravel; - } - } - return root.animationOffset; - } - - readonly property real computedScaleCollapsed: root.animationScaleCollapsed - - // openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1). - QtObject { - id: morph - property real openProgress: root.shouldBeVisible ? 1 : 0 - Behavior on openProgress { - enabled: root.animationsEnabled - NumberAnimation { - duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible) - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - } - - readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress) - readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress) - readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress - - onAnimXChanged: if (root.frameOwnsConnectedChrome) - root._queueAnimSync() - onAnimYChanged: if (root.frameOwnsConnectedChrome) - root._queueAnimSync() + clip: root.frameOwnsConnectedChrome Item { - id: contentContainer - anchors.centerIn: parent - width: parent.width - height: parent.height - clip: false + id: modalContainer + x: Theme.snap(animX, root.dpr) + y: Theme.snap(animY, root.dpr) + + width: root.alignedWidth + height: root.alignedHeight + + MouseArea { + anchors.fill: parent + enabled: root.shouldBeVisible + hoverEnabled: false + acceptedButtons: Qt.AllButtons + onPressed: mouse.accepted = true + onClicked: mouse.accepted = true + z: -1 + } + + readonly property bool slide: root.animationType === "slide" + readonly property bool directionalEffect: Theme.isDirectionalEffect + readonly property bool depthEffect: Theme.isDepthEffect + readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8) + readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36) + readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5 + readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5 + readonly property real customDistLeft: customAnchorX + readonly property real customDistRight: root.screenWidth - customAnchorX + readonly property real customDistTop: customAnchorY + readonly property real customDistBottom: root.screenHeight - customAnchorY + // Connected emergence: travel from the resolved bar edge, matching DankPopout cadence. + readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL) + readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL) + readonly property real offsetX: { + if (root.frameOwnsConnectedChrome) { + switch (root.resolvedConnectedBarSide) { + case "left": + return -connectedEmergenceTravelX; + case "right": + return connectedEmergenceTravelX; + } + return 0; + } + if (slide && !directionalEffect && !depthEffect) + return 15; + if (directionalEffect) { + switch (root.positioning) { + case "top-right": + return 0; + case "custom": + if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom) + return -directionalTravel; + if (customDistRight <= customDistTop && customDistRight <= customDistBottom) + return directionalTravel; + return 0; + default: + return 0; + } + } + if (depthEffect) { + switch (root.positioning) { + case "top-right": + return 0; + case "custom": + if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom) + return -depthTravel; + if (customDistRight <= customDistTop && customDistRight <= customDistBottom) + return depthTravel; + return 0; + default: + return 0; + } + } + return 0; + } + readonly property real offsetY: { + if (root.frameOwnsConnectedChrome) { + switch (root.resolvedConnectedBarSide) { + case "top": + return -connectedEmergenceTravelY; + case "bottom": + return connectedEmergenceTravelY; + } + return 0; + } + if (slide && !directionalEffect && !depthEffect) + return -30; + if (directionalEffect) { + switch (root.positioning) { + case "top-right": + return -Math.max(directionalTravel * 0.65, 96); + case "custom": + if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight) + return -directionalTravel; + if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight) + return directionalTravel; + return 0; + default: + // Default to sliding down from top when centered + return -Math.max(directionalTravel, root.screenHeight * 0.24); + } + } + if (depthEffect) { + switch (root.positioning) { + case "top-right": + return -depthTravel * 0.75; + case "custom": + if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight) + return -depthTravel; + if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight) + return depthTravel; + return depthTravel * 0.45; + default: + return -depthTravel; + } + } + return root.animationOffset; + } + + readonly property real computedScaleCollapsed: root.animationScaleCollapsed + + // openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1). + QtObject { + id: morph + property real openProgress: root.shouldBeVisible ? 1 : 0 + Behavior on openProgress { + enabled: root.animationsEnabled + NumberAnimation { + duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible) + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve + } + } + } + + readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress) + readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress) + readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress + + onAnimXChanged: if (root.frameOwnsConnectedChrome) + root._queueAnimSync() + onAnimYChanged: if (root.frameOwnsConnectedChrome) + root._queueAnimSync() Item { - id: animatedContent - anchors.fill: parent + id: contentContainer + anchors.centerIn: parent + width: parent.width + height: parent.height clip: false - property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0) - - opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0) - scale: modalContainer.scaleValue - transformOrigin: Item.Center - - Behavior on opacity { - enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect) - NumberAnimation { - duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - Behavior on publishedOpacity { - enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect) - NumberAnimation { - duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - ElevationShadow { - id: modalShadowLayer + Item { + id: animatedContent anchors.fill: parent - level: root.shadowLevel - fallbackOffset: root.shadowFallbackOffset - targetRadius: root.effectiveCornerRadius - targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor - borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor - borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth - shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" - } - - Rectangle { - anchors.fill: parent - radius: root.effectiveCornerRadius - color: "transparent" - border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor - border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth - z: 100 - } - - FocusScope { - anchors.fill: parent - focus: root.shouldBeVisible clip: false - Item { - id: directContentWrapper + property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0) + + opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0) + scale: modalContainer.scaleValue + transformOrigin: Item.Center + + Behavior on opacity { + enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect) + NumberAnimation { + duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve + } + } + + Behavior on publishedOpacity { + enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect) + NumberAnimation { + duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve + } + } + + ElevationShadow { + id: modalShadowLayer anchors.fill: parent - visible: root.directContent !== null - focus: true + level: root.shadowLevel + fallbackOffset: root.shadowFallbackOffset + targetRadius: root.effectiveCornerRadius + targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor + borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor + borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth + shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" + } + + Rectangle { + anchors.fill: parent + radius: root.effectiveCornerRadius + color: "transparent" + border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor + border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth + z: 100 + } + + FocusScope { + anchors.fill: parent + focus: root.shouldBeVisible clip: false - Component.onCompleted: { - if (root.directContent) { - root.directContent.parent = directContentWrapper; - root.directContent.anchors.fill = directContentWrapper; - Qt.callLater(() => root.directContent.forceActiveFocus()); - } - } + Item { + id: directContentWrapper + anchors.fill: parent + visible: root.directContent !== null + focus: true + clip: false - Connections { - target: root - function onDirectContentChanged() { + Component.onCompleted: { if (root.directContent) { root.directContent.parent = directContentWrapper; root.directContent.anchors.fill = directContentWrapper; Qt.callLater(() => root.directContent.forceActiveFocus()); } } + + Connections { + target: root + function onDirectContentChanged() { + if (root.directContent) { + root.directContent.parent = directContentWrapper; + root.directContent.anchors.fill = directContentWrapper; + Qt.callLater(() => root.directContent.forceActiveFocus()); + } + } + } } - } - Loader { - id: contentLoader - anchors.fill: parent - active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible) - asynchronous: false - focus: true - clip: false - visible: root.directContent === null + Loader { + id: contentLoader + anchors.fill: parent + active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible) + asynchronous: false + focus: true + clip: false + visible: root.directContent === null - onLoaded: { - if (item) { - Qt.callLater(() => item.forceActiveFocus()); + onLoaded: { + if (item) { + Qt.callLater(() => item.forceActiveFocus()); + } } } } diff --git a/quickshell/Modals/Common/DankModalStandalone.qml b/quickshell/Modals/Common/DankModalStandalone.qml index d91c5344..cc541cf1 100644 --- a/quickshell/Modals/Common/DankModalStandalone.qml +++ b/quickshell/Modals/Common/DankModalStandalone.qml @@ -205,6 +205,7 @@ Item { id: clickCatcher visible: false color: "transparent" + updatesEnabled: false WlrLayershell.namespace: root.layerNamespace + ":clickcatcher" WlrLayershell.layer: WlrLayershell.Top diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index 48b81435..4d7a0b22 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -203,25 +203,9 @@ Item { } readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight - // For directional/depth: window extends from screen top (content slides within) - // For standard: small window tightly around the modal + shadow padding - readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect - // Content window geometry - readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr) - readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr)) - readonly property real _cwWidth: alignedWidth + shadowPad * 2 - readonly property real _cwHeight: { - if (launcherArcExtenderActive) - return _connectedChromeHeight; - if (Theme.isDirectionalEffect && !Theme.isConnectedEffect) - return screenHeight + shadowPad; - if (Theme.isDepthEffect) - return alignedY + alignedHeight + shadowPad; - return alignedHeight + shadowPad * 2; - } - // Where the content container sits inside the content window - readonly property real _ccX: shadowPad - readonly property real _ccY: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad) + // Where the content container sits inside the full-surface content window (screen coordinates) + readonly property real _ccX: _connectedChromeX + readonly property real _ccY: _connectedChromeY signal dialogClosed @@ -429,7 +413,6 @@ Item { var focusedScreen = CompositorService.getFocusedScreen(); if (focusedScreen) { - backgroundWindow.screen = focusedScreen; contentWindow.screen = focusedScreen; } @@ -439,7 +422,6 @@ Item { // Make windows visible but do NOT request keyboard focus yet ModalManager.openModal(modalHandle); spotlightOpen = true; - backgroundWindow.visible = true; contentWindow.visible = true; // Load content and initialize (but no forceActiveFocus — that's deferred) @@ -519,7 +501,6 @@ Item { isClosing = false; contentVisible = false; contentWindow.visible = false; - backgroundWindow.visible = false; if (root.unloadContentOnClose) launcherContentLoader.active = false; dialogClosed(); @@ -588,7 +569,6 @@ Item { root._releaseModalChrome(); root._windowEnabled = false; - backgroundWindow.screen = newScreen; contentWindow.screen = newScreen; Qt.callLater(() => { root._windowEnabled = true; @@ -596,73 +576,6 @@ Item { } } - PanelWindow { - id: backgroundWindow - visible: false - color: "transparent" - - readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0) - readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0) - readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0) - readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0) - - WlrLayershell.namespace: "dms:spotlight:bg" - WlrLayershell.layer: root.effectiveLauncherLayer - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - WlrLayershell.margins { - top: backgroundWindow._topMargin - bottom: backgroundWindow._bottomMargin - left: backgroundWindow._leftMargin - right: backgroundWindow._rightMargin - } - - anchors { - top: true - bottom: true - left: true - right: true - } - - mask: Region { - item: (spotlightOpen || isClosing) ? bgFullScreenMask : null - - Region { - item: bgContentHole - intersection: Intersection.Subtract - } - } - - Item { - id: bgFullScreenMask - anchors.fill: parent - } - - Item { - id: bgContentHole - visible: false - x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin - y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin - width: root.alignedWidth - height: root.contentSurfaceHeight - } - - Rectangle { - id: backgroundDarken - anchors.fill: parent - color: "black" - opacity: 0 - visible: false - } - - MouseArea { - anchors.fill: parent - enabled: spotlightOpen - onClicked: root.hide() - } - } - PanelWindow { id: contentWindow visible: false @@ -687,18 +600,28 @@ Item { anchors { left: true top: true + right: true + bottom: true } - WlrLayershell.margins { - left: root._cwMarginLeft - top: root._cwMarginTop - } - - implicitWidth: root._cwWidth - implicitHeight: root._cwHeight - mask: Region { - item: contentInputMask + item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask + + Region { + item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null + } + } + + // Dismissable area: everything except the bar/dock strips, so bar + // widgets stay clickable for widget-to-widget transfer. + Item { + id: dismissArea + visible: false + anchors.fill: parent + anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0) + anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0) + anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0) + anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0) } Item { @@ -710,16 +633,31 @@ Item { height: root.contentSurfaceHeight } + MouseArea { + anchors.fill: dismissArea + enabled: root.spotlightOpen + z: -2 + onClicked: root.hide() + } + Item { id: contentContainer - // For directional/depth: contentContainer is at alignedY from window top (window starts at screen top) - // For standard: contentContainer is at shadowPad from window top (window starts near modal) x: root._ccX y: root._ccY width: root.alignedWidth height: root.contentSurfaceHeight + MouseArea { + anchors.fill: parent + enabled: root.spotlightOpen + hoverEnabled: false + acceptedButtons: Qt.AllButtons + onPressed: mouse.accepted = true + onClicked: mouse.accepted = true + z: -1 + } + readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1 readonly property bool dockTop: dockEdge === 0 readonly property bool dockBottom: dockEdge === 1 diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml index fc907205..3752899c 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml @@ -84,14 +84,14 @@ Item { readonly property real alignedX: Theme.snap(modalX, dpr) readonly property real alignedY: Theme.snap(modalY, dpr) - // Extra headroom above the window for the slide-in animation + // Extra headroom above the content for the slide-in animation readonly property real _animHeadroom: 16 readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr)) readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr)) readonly property real contentX: Theme.snap(alignedX - windowX, dpr) readonly property real contentY: Theme.snap(alignedY - windowY, dpr) - readonly property real windowWidth: alignedWidth + contentX + shadowPad readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr) + readonly property real windowWidth: alignedWidth + contentX + shadowPad readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) @@ -114,6 +114,7 @@ Item { } } readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0 + readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken signal dialogClosed @@ -267,8 +268,9 @@ Item { PanelWindow { id: clickCatcher screen: launcherWindow.screen - visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken + visible: (spotlightOpen || isClosing) && !root.useSingleWindow color: "transparent" + updatesEnabled: false WlrLayershell.namespace: "dms:spotlight:clickcatcher" WlrLayershell.layer: root.effectiveLauncherLayer @@ -339,19 +341,19 @@ Item { anchors { top: true left: true - right: root.useBackgroundDarken - bottom: root.useBackgroundDarken + right: root.useSingleWindow + bottom: root.useSingleWindow } WlrLayershell.margins { - left: root.useBackgroundDarken ? 0 : root.windowX - top: root.useBackgroundDarken ? 0 : root.windowY + left: root.useSingleWindow ? 0 : root.windowX + top: root.useSingleWindow ? 0 : root.windowY right: 0 bottom: 0 } - implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth - implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight + implicitWidth: root.useSingleWindow ? 0 : root.windowWidth + implicitHeight: root.useSingleWindow ? 0 : root.windowHeight mask: Region { item: inputMask @@ -361,15 +363,15 @@ Item { id: inputMask visible: false color: "transparent" - x: root.useBackgroundDarken ? 0 : modalContainer.x - y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset - width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth - height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH + x: root.useSingleWindow ? 0 : modalContainer.x + y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset + width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth + height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH } MouseArea { anchors.fill: parent - enabled: root.useBackgroundDarken && spotlightOpen + enabled: root.useSingleWindow && spotlightOpen z: -2 onClicked: root.hide() } @@ -393,13 +395,23 @@ Item { Item { id: modalContainer - x: root.useBackgroundDarken ? root.alignedX : root.contentX - y: root.useBackgroundDarken ? root.alignedY : root.contentY + x: root.useSingleWindow ? root.alignedX : root.contentX + y: root.useSingleWindow ? root.alignedY : root.contentY width: root.alignedWidth height: root._animatedContentH visible: _renderActive z: 0 + MouseArea { + anchors.fill: parent + enabled: spotlightOpen + hoverEnabled: false + acceptedButtons: Qt.AllButtons + onPressed: mouse.accepted = true + onClicked: mouse.accepted = true + z: -1 + } + property bool _renderActive: contentVisible property real slideOffset: contentVisible ? 0 : -root._animHeadroom diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml index 51a6ab98..306093e6 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml @@ -80,6 +80,7 @@ Item { readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground + readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, { "allow": ["top", "overlay"], @@ -303,8 +304,9 @@ Item { PanelWindow { id: clickCatcher screen: launcherWindow.screen - visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken + visible: (spotlightOpen || isClosing) && !root.useSingleWindow color: "transparent" + updatesEnabled: false WlrLayershell.namespace: "dms:spotlight:clickcatcher" WlrLayershell.layer: root.effectiveLauncherLayer @@ -375,19 +377,19 @@ Item { anchors { top: true left: true - right: root.useBackgroundDarken - bottom: root.useBackgroundDarken + right: root.useSingleWindow + bottom: root.useSingleWindow } WlrLayershell.margins { - left: root.useBackgroundDarken ? 0 : root.windowX - top: root.useBackgroundDarken ? 0 : root.windowY + left: root.useSingleWindow ? 0 : root.windowX + top: root.useSingleWindow ? 0 : root.windowY right: 0 bottom: 0 } - implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth - implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight + implicitWidth: root.useSingleWindow ? 0 : root.windowWidth + implicitHeight: root.useSingleWindow ? 0 : root.windowHeight mask: Region { item: launcherInputMask @@ -397,15 +399,15 @@ Item { id: launcherInputMask visible: false color: "transparent" - x: root.useBackgroundDarken ? 0 : modalContainer.x - y: root.useBackgroundDarken ? 0 : modalContainer.y - width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width - height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height + x: root.useSingleWindow ? 0 : modalContainer.x + y: root.useSingleWindow ? 0 : modalContainer.y + width: root.useSingleWindow ? launcherWindow.width : modalContainer.width + height: root.useSingleWindow ? launcherWindow.height : modalContainer.height } MouseArea { anchors.fill: parent - enabled: root.useBackgroundDarken && spotlightOpen + enabled: root.useSingleWindow && spotlightOpen z: -2 onClicked: root.hide() } @@ -429,13 +431,23 @@ Item { Item { id: modalContainer - x: root.useBackgroundDarken ? root.alignedX : root.contentX - y: root.useBackgroundDarken ? root.alignedY : root.contentY + x: root.useSingleWindow ? root.alignedX : root.contentX + y: root.useSingleWindow ? root.alignedY : root.contentY width: root.alignedWidth height: root.alignedHeight visible: _renderActive z: 0 + MouseArea { + anchors.fill: parent + enabled: spotlightOpen + hoverEnabled: false + acceptedButtons: Qt.AllButtons + onPressed: mouse.accepted = true + onClicked: mouse.accepted = true + z: -1 + } + property bool _renderActive: contentVisible property real publishedScale: contentVisible ? 1 : 0.96 property real publishedOpacity: contentVisible ? 1 : 0 diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index 4385a887..e302f598 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -747,16 +747,36 @@ Variants { onHeightChanged: dock._syncDockChromeState() } - ConnectedShape { + Item { + id: dockConnectedChrome visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive - barSide: dock.connectedBarSide - bodyWidth: dockBackground.width - bodyHeight: dockBackground.height - connectorRadius: Theme.connectedCornerRadius - surfaceRadius: dock.surfaceRadius - fillColor: dock.surfaceColor - x: dockBackground.x - bodyX - y: dockBackground.y - bodyY + readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius + readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0 + readonly property real bodyRadius: dock.surfaceRadius + readonly property bool barTop: dock.connectedBarSide === "top" + readonly property bool barBottom: dock.connectedBarSide === "bottom" + readonly property bool barLeft: dock.connectedBarSide === "left" + readonly property bool barRight: dock.connectedBarSide === "right" + + x: dockBackground.x - extraLeft + y: dockBackground.y - extraTop + width: dockBackground.width + extraLeft * 2 + height: dockBackground.height + extraTop * 2 + + ShaderEffect { + anchors.fill: parent + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb") + + property real widthPx: width + property real heightPx: height + property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a) + property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0) + property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0) + property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0) + property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height) + property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius) + property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0) + } } Shape { diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index a5d41c08..c9ea1823 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -49,10 +49,6 @@ PanelWindow { readonly property var _dockDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "dock") readonly property var _notifDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "notification") readonly property var _modalDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "modal") - readonly property var _popoutState: ConnectedModeState.legacySurfaceState(win._screenName, "popout") - readonly property var _dockState: ConnectedModeState.legacySurfaceState(win._screenName, "dock") - readonly property var _notifState: ConnectedModeState.legacySurfaceState(win._screenName, "notification") - readonly property var _modalState: ConnectedModeState.legacySurfaceState(win._screenName, "modal") readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen) readonly property string _barSide: { @@ -68,7 +64,7 @@ PanelWindow { readonly property real _ccr: Theme.connectedCornerRadius readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.barSide) - readonly property bool _modalHorizontal: ConnectorGeometry.isHorizontal(win._modalState.barSide) + readonly property bool _modalHorizontal: SurfaceGeometry.isHorizontal(win._modalDescriptor.barSide) readonly property var _popoutBodyGeometry: SurfaceGeometry.animatedBodyRect(win._popoutDescriptor, win._dpr) readonly property var _modalBodyGeometry: SurfaceGeometry.animatedBodyRect(win._modalDescriptor, win._dpr) readonly property var _notifBodyGeometry: SurfaceGeometry.bodyRect(win._notifDescriptor, win._dpr) @@ -86,15 +82,15 @@ PanelWindow { readonly property real _dockConnectorRadiusValue: { if (!_dockBodyBlurAnchor._active) return win._ccr; - const thickness = (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? _dockBodyBlurAnchor.width : _dockBodyBlurAnchor.height; + const thickness = SurfaceGeometry.isVertical(win._dockDescriptor.barSide) ? _dockBodyBlurAnchor.width : _dockBodyBlurAnchor.height; const bodyRadius = win._dockBodyBlurRadiusValue; const maxConnectorRadius = Math.max(0, thickness - bodyRadius - win._seamOverlap); return Math.max(0, Math.min(win._ccr, bodyRadius, maxConnectorRadius)); } - readonly property real _notifSideUnderlapValue: ConnectorGeometry.isVertical(win._notifState.barSide) ? win._seamOverlap : 0 - readonly property real _notifStartUnderlapValue: win._notifState.omitStartConnector ? win._seamOverlap : 0 - readonly property real _notifEndUnderlapValue: win._notifState.omitEndConnector ? win._seamOverlap : 0 + readonly property real _notifSideUnderlapValue: SurfaceGeometry.isVertical(win._notifDescriptor.barSide) ? win._seamOverlap : 0 + readonly property real _notifStartUnderlapValue: win._notifDescriptor.omitStartConnector ? win._seamOverlap : 0 + readonly property real _notifEndUnderlapValue: win._notifDescriptor.omitEndConnector ? win._seamOverlap : 0 // Theme.snap rounds to integer pixel: equal rounded values suppress // downstream Changed during sub-pixel morph jitter. @@ -284,7 +280,7 @@ PanelWindow { Region { id: _popoutBodyBlurCap - readonly property string _side: win._popoutState.barSide + readonly property string _side: win._popoutDescriptor.barSide readonly property real _capThickness: win._popoutBlurCapThickness() readonly property bool _active: _popoutBodyBlurAnchor._active && _capThickness > 0 && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0 readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _popoutBodyBlurAnchor.width)) : _popoutBodyBlurAnchor.width @@ -311,7 +307,7 @@ PanelWindow { id: _popoutLeftConnectorCutout readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "left") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "left") readonly property real _radius: win._popoutConnectorRadiusLeft intersection: Intersection.Subtract @@ -338,7 +334,7 @@ PanelWindow { id: _popoutRightConnectorCutout readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "right") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "right") readonly property real _radius: win._popoutConnectorRadiusRight intersection: Intersection.Subtract @@ -389,8 +385,8 @@ PanelWindow { id: _popoutFarStartConnectorCutout readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._popoutState.barSide, "left") - readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "left") + readonly property string _barSide: win._farConnectorBarSide(win._popoutDescriptor.barSide, "left") + readonly property string _placement: win._farConnectorPlacement(win._popoutDescriptor.barSide, "left") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectivePopoutFarStartCcr @@ -418,8 +414,8 @@ PanelWindow { id: _popoutFarEndConnectorCutout readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._popoutState.barSide, "right") - readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "right") + readonly property string _barSide: win._farConnectorBarSide(win._popoutDescriptor.barSide, "right") + readonly property string _placement: win._farConnectorPlacement(win._popoutDescriptor.barSide, "right") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectivePopoutFarEndCcr @@ -446,7 +442,7 @@ PanelWindow { Region { id: _dockBodyBlurCap - readonly property string _side: win._dockState.barSide + readonly property string _side: win._dockDescriptor.barSide readonly property bool _active: _dockBodyBlurAnchor._active && _dockBodyBlurAnchor.width > 0 && _dockBodyBlurAnchor.height > 0 readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.width)) : _dockBodyBlurAnchor.width readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.height)) : _dockBodyBlurAnchor.height @@ -471,7 +467,7 @@ PanelWindow { id: _dockLeftConnectorCutout readonly property bool _active: _dockLeftConnectorBlurAnchor.width > 0 && _dockLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "left") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "left") intersection: Intersection.Subtract radius: win._dockConnectorRadiusValue @@ -496,7 +492,7 @@ PanelWindow { id: _dockRightConnectorCutout readonly property bool _active: _dockRightConnectorBlurAnchor.width > 0 && _dockRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "right") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "right") intersection: Intersection.Subtract radius: win._dockConnectorRadiusValue @@ -522,7 +518,7 @@ PanelWindow { Region { id: _notifBodyBlurCap - readonly property string _side: win._notifState.barSide + readonly property string _side: win._notifDescriptor.barSide readonly property real _capRadius: win._effectiveNotifMaxCcr readonly property bool _active: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 && _capRadius > 0 readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capRadius, _notifBodySceneBlurAnchor.width)) : _notifBodySceneBlurAnchor.width @@ -549,7 +545,7 @@ PanelWindow { id: _notifLeftConnectorCutout readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "left") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "left") readonly property real _radius: win._notifConnectorRadiusLeft intersection: Intersection.Subtract @@ -576,7 +572,7 @@ PanelWindow { id: _notifRightConnectorCutout readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "right") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "right") readonly property real _radius: win._notifConnectorRadiusRight intersection: Intersection.Subtract @@ -627,8 +623,8 @@ PanelWindow { id: _notifFarStartConnectorCutout readonly property bool _active: _notifFarStartConnectorBlurAnchor.width > 0 && _notifFarStartConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "left") - readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "left") + readonly property string _barSide: win._farConnectorBarSide(win._notifDescriptor.barSide, "left") + readonly property string _placement: win._farConnectorPlacement(win._notifDescriptor.barSide, "left") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectiveNotifFarStartCcr @@ -656,8 +652,8 @@ PanelWindow { id: _notifFarEndConnectorCutout readonly property bool _active: _notifFarEndConnectorBlurAnchor.width > 0 && _notifFarEndConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "right") - readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "right") + readonly property string _barSide: win._farConnectorBarSide(win._notifDescriptor.barSide, "right") + readonly property string _placement: win._farConnectorPlacement(win._notifDescriptor.barSide, "right") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectiveNotifFarEndCcr @@ -684,7 +680,7 @@ PanelWindow { Region { id: _modalBodyBlurCap - readonly property string _side: win._modalState.barSide + readonly property string _side: win._modalDescriptor.barSide readonly property real _capThickness: win._modalBlurCapThickness() readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0 readonly property int _capWidth: (_side === "left" || _side === "right") ? Math.round(Math.min(_capThickness, _modalBodyBlurAnchor.width)) : _modalBodyBlurAnchor.width @@ -711,7 +707,7 @@ PanelWindow { id: _modalLeftConnectorCutout readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "left") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "left") readonly property real _radius: win._modalConnectorRadiusLeft intersection: Intersection.Subtract @@ -738,7 +734,7 @@ PanelWindow { id: _modalRightConnectorCutout readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "right") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "right") readonly property real _radius: win._modalConnectorRadiusRight intersection: Intersection.Subtract @@ -789,8 +785,8 @@ PanelWindow { id: _modalFarStartConnectorCutout readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "left") - readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "left") + readonly property string _barSide: win._farConnectorBarSide(win._modalDescriptor.barSide, "left") + readonly property string _placement: win._farConnectorPlacement(win._modalDescriptor.barSide, "left") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectiveModalFarStartCcr @@ -818,8 +814,8 @@ PanelWindow { id: _modalFarEndConnectorCutout readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "right") - readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "right") + readonly property string _barSide: win._farConnectorBarSide(win._modalDescriptor.barSide, "right") + readonly property string _placement: win._farConnectorPlacement(win._modalDescriptor.barSide, "right") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectiveModalFarEndCcr @@ -835,7 +831,7 @@ PanelWindow { // Notif body scene rect, accounting for start/end/side underlaps per bar orientation. function _notifBodyScene() { - const isHoriz = ConnectorGeometry.isHorizontal(win._notifState.barSide); + const isHoriz = SurfaceGeometry.isHorizontal(win._notifDescriptor.barSide); const start = win._notifStartUnderlapValue; const end = win._notifEndUnderlapValue; const side = win._notifSideUnderlapValue; @@ -848,7 +844,7 @@ PanelWindow { }; } return { - "x": _notifBodyBlurAnchor.x - (win._notifState.barSide === "left" ? side : 0), + "x": _notifBodyBlurAnchor.x - (win._notifDescriptor.barSide === "left" ? side : 0), "y": _notifBodyBlurAnchor.y - start, "width": _notifBodyBlurAnchor.width + side, "height": _notifBodyBlurAnchor.height + start + end @@ -871,7 +867,7 @@ PanelWindow { function _unifiedSurfaces() { const arr = []; const p = win._popoutBodyGeometry; - if (win._popoutDescriptor.visible && win._popoutState.screen === win._screenName && p.width > 0 && p.height > 0) + if (win._popoutDescriptor.visible && win._popoutDescriptor.screenName === win._screenName && p.width > 0 && p.height > 0) arr.push({ "side": win._popoutDescriptor.barSide, "body": {"x": p.x, "y": p.y, "width": p.width, "height": p.height}, diff --git a/quickshell/Shaders/frag/connected_chrome.frag b/quickshell/Shaders/frag/connected_chrome.frag index bc218460..5e8f63b7 100644 --- a/quickshell/Shaders/frag/connected_chrome.frag +++ b/quickshell/Shaders/frag/connected_chrome.frag @@ -2,9 +2,8 @@ // Popout-local connected chrome as a signed-distance field: the body rounded // rect smooth-unioned against the bar-edge half-plane, so the connector -// fillets form analytically — the SDF twin of the old ConnectedShape + -// ConnectedCorner + MultiEffect FBO. Key + ambient shadows sample the same -// field; shadow is masked outside the silhouette. +// fillets form analytically. Key + ambient shadows sample the same field; +// shadow is masked outside the silhouette. layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; diff --git a/quickshell/Widgets/ConnectedCorner.qml b/quickshell/Widgets/ConnectedCorner.qml deleted file mode 100644 index 44fb9c15..00000000 --- a/quickshell/Widgets/ConnectedCorner.qml +++ /dev/null @@ -1,151 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import "../Common/ConnectorGeometry.js" as ConnectorGeometry - -// Concave arc connector filling the gap between a bar corner and an adjacent surface. -// -// NOTE: FrameWindow now uses ConnectedShape.qml for frame-owned connected chrome -// (unified single-path rendering). This component is still used by DankPopout's -// own shadow source for non-frame-owned chrome (popouts on non-frame screens). - -Item { - id: root - - property string barSide: "top" - property string placement: "left" - property real spacing: 4 - property real connectorRadius: 12 - property color color: "transparent" - property real edgeStrokeWidth: 0 - property color edgeStrokeColor: color - property real dpr: 1 - - readonly property bool isHorizontalBar: barSide === "top" || barSide === "bottom" - readonly property bool isPlacementLeft: placement === "left" - readonly property real _edgeStrokeWidth: Math.max(0, edgeStrokeWidth) - readonly property string arcCorner: ConnectorGeometry.arcCorner(barSide, placement) - readonly property real pathStartX: { - switch (arcCorner) { - case "topLeft": - return width; - case "topRight": - case "bottomLeft": - return 0; - default: - return 0; - } - } - readonly property real pathStartY: { - switch (arcCorner) { - case "bottomRight": - return height; - default: - return 0; - } - } - readonly property real firstLineX: { - switch (arcCorner) { - case "topLeft": - case "bottomLeft": - return width; - default: - return 0; - } - } - readonly property real firstLineY: { - switch (arcCorner) { - case "topLeft": - case "topRight": - return height; - default: - return 0; - } - } - readonly property real secondLineX: { - switch (arcCorner) { - case "topRight": - case "bottomLeft": - case "bottomRight": - return width; - default: - return 0; - } - } - readonly property real secondLineY: { - switch (arcCorner) { - case "topLeft": - case "topRight": - case "bottomLeft": - return height; - default: - return 0; - } - } - readonly property real arcCenterX: arcCorner === "topRight" || arcCorner === "bottomRight" ? width : 0 - readonly property real arcCenterY: arcCorner === "bottomLeft" || arcCorner === "bottomRight" ? height : 0 - readonly property real arcStartAngle: { - switch (arcCorner) { - case "topLeft": - case "topRight": - return 90; - case "bottomLeft": - return 0; - default: - return -90; - } - } - readonly property real arcSweepAngle: { - switch (arcCorner) { - case "topRight": - return 90; - default: - return -90; - } - } - - width: isHorizontalBar ? connectorRadius : (spacing + connectorRadius) - height: isHorizontalBar ? (spacing + connectorRadius) : connectorRadius - - Shape { - x: -root._edgeStrokeWidth - y: -root._edgeStrokeWidth - width: root.width + root._edgeStrokeWidth * 2 - height: root.height + root._edgeStrokeWidth * 2 - asynchronous: false - antialiasing: true - preferredRendererType: Shape.CurveRenderer - layer.enabled: true - layer.smooth: true - layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0) - - ShapePath { - fillColor: root.color - strokeColor: root._edgeStrokeWidth > 0 ? root.edgeStrokeColor : "transparent" - strokeWidth: root._edgeStrokeWidth * 2 - joinStyle: ShapePath.RoundJoin - capStyle: ShapePath.RoundCap - fillRule: ShapePath.WindingFill - startX: root.pathStartX + root._edgeStrokeWidth - startY: root.pathStartY + root._edgeStrokeWidth - - PathLine { - x: root.firstLineX + root._edgeStrokeWidth - y: root.firstLineY + root._edgeStrokeWidth - } - - PathLine { - x: root.secondLineX + root._edgeStrokeWidth - y: root.secondLineY + root._edgeStrokeWidth - } - - PathAngleArc { - centerX: root.arcCenterX + root._edgeStrokeWidth - centerY: root.arcCenterY + root._edgeStrokeWidth - radiusX: root.connectorRadius - radiusY: root.connectorRadius - startAngle: root.arcStartAngle - sweepAngle: root.arcSweepAngle - } - } - } -} diff --git a/quickshell/Widgets/ConnectedShape.qml b/quickshell/Widgets/ConnectedShape.qml deleted file mode 100644 index 4622a8b3..00000000 --- a/quickshell/Widgets/ConnectedShape.qml +++ /dev/null @@ -1,414 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import qs.Common - -// Unified connected silhouette: body + near/far concave arcs as one ShapePath. -// Keeping the connected chrome in one path avoids sibling alignment seams. - -Item { - id: root - - property string barSide: "top" - - property real bodyWidth: 0 - property real bodyHeight: 0 - - property real connectorRadius: 12 - property real startConnectorRadius: connectorRadius - property real endConnectorRadius: connectorRadius - property real farStartConnectorRadius: 0 - property real farEndConnectorRadius: 0 - - property real surfaceRadius: 12 - - property color fillColor: "transparent" - - readonly property bool _horiz: barSide === "top" || barSide === "bottom" - readonly property real _sc: Math.max(0, startConnectorRadius) - readonly property real _ec: Math.max(0, endConnectorRadius) - readonly property real _fsc: Math.max(0, farStartConnectorRadius) - readonly property real _fec: Math.max(0, farEndConnectorRadius) - readonly property real _firstCr: barSide === "left" ? _sc : _ec - readonly property real _secondCr: barSide === "left" ? _ec : _sc - readonly property real _firstFarCr: barSide === "left" ? _fsc : _fec - readonly property real _secondFarCr: barSide === "left" ? _fec : _fsc - readonly property real _farExtent: Math.max(_fsc, _fec) - readonly property real _sr: Math.max(0, Math.min(surfaceRadius, (_horiz ? bodyWidth : bodyHeight) / 2, (_horiz ? bodyHeight : bodyWidth) / 2)) - readonly property real _firstSr: _firstFarCr > 0 ? 0 : _sr - readonly property real _secondSr: _secondFarCr > 0 ? 0 : _sr - readonly property real _firstFarInset: _firstFarCr > 0 ? _firstFarCr : _firstSr - readonly property real _secondFarInset: _secondFarCr > 0 ? _secondFarCr : _secondSr - - // Root-level aliases — PathArc/PathLine elements can't use `parent`. - readonly property real _bw: bodyWidth - readonly property real _bh: bodyHeight - readonly property real _bodyLeft: _horiz ? _sc : (barSide === "right" ? _farExtent : 0) - readonly property real _bodyRight: _bodyLeft + _bw - readonly property real _bodyTop: _horiz ? (barSide === "bottom" ? _farExtent : 0) : _sc - readonly property real _bodyBottom: _bodyTop + _bh - readonly property real _totalW: _horiz ? _bw + _sc + _ec : _bw + _farExtent - readonly property real _totalH: _horiz ? _bh + _farExtent : _bh + _sc + _ec - - width: _totalW - height: _totalH - - readonly property real bodyX: root._bodyLeft - readonly property real bodyY: root._bodyTop - - Shape { - anchors.fill: parent - asynchronous: false - preferredRendererType: Shape.CurveRenderer - antialiasing: true - - ShapePath { - fillColor: root.fillColor - strokeWidth: -1 - fillRule: ShapePath.WindingFill - - // CW path: bar edge → concave arc → body → convex arc → far edge → convex arc → body → concave arc - - startX: root.barSide === "right" ? root._totalW : 0 - startY: { - switch (root.barSide) { - case "bottom": - return root._totalH; - case "left": - return root._totalH; - case "right": - return 0; - default: - return 0; - } - } - - // Bar edge - PathLine { - x: { - switch (root.barSide) { - case "left": - return 0; - case "right": - return root._totalW; - default: - return root._totalW; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._totalH; - case "left": - return 0; - case "right": - return root._totalH; - default: - return 0; - } - } - } - - // Concave arc 1 - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return root._firstCr; - case "right": - return -root._firstCr; - default: - return -root._firstCr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return -root._firstCr; - case "left": - return root._firstCr; - case "right": - return -root._firstCr; - default: - return root._firstCr; - } - } - radiusX: root._firstCr - radiusY: root._firstCr - direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise - } - - // Body edge to first convex corner - PathLine { - x: { - switch (root.barSide) { - case "left": - return root._bodyRight - root._firstSr; - case "right": - return root._bodyLeft + root._firstSr; - default: - return root._bodyRight; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._bodyTop + root._firstSr; - case "left": - return root._bodyTop; - case "right": - return root._bodyBottom; - default: - return root._bodyBottom - root._firstSr; - } - } - } - - // Convex arc 1 - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return root._firstSr; - case "right": - return -root._firstSr; - default: - return -root._firstSr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return -root._firstSr; - case "left": - return root._firstSr; - case "right": - return -root._firstSr; - default: - return root._firstSr; - } - } - radiusX: root._firstSr - radiusY: root._firstSr - direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise - } - - // Opposite-side connector 1 - PathLine { - x: { - switch (root.barSide) { - case "left": - return root._firstFarCr > 0 ? root._bodyRight + root._firstFarCr : root._bodyRight; - case "right": - return root._firstFarCr > 0 ? root._bodyLeft - root._firstFarCr : root._bodyLeft; - default: - return root._firstFarCr > 0 ? root._bodyRight : root._bodyRight - root._firstSr; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._firstFarCr > 0 ? root._bodyTop - root._firstFarCr : root._bodyTop; - case "left": - return root._firstFarCr > 0 ? root._bodyTop : root._bodyTop + root._firstSr; - case "right": - return root._firstFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._firstSr; - default: - return root._firstFarCr > 0 ? root._bodyBottom + root._firstFarCr : root._bodyBottom; - } - } - } - - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return -root._firstFarCr; - case "right": - return root._firstFarCr; - default: - return -root._firstFarCr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return root._firstFarCr; - case "left": - return root._firstFarCr; - case "right": - return -root._firstFarCr; - default: - return -root._firstFarCr; - } - } - radiusX: root._firstFarCr - radiusY: root._firstFarCr - direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise - } - - // Far edge - PathLine { - x: { - switch (root.barSide) { - case "left": - return root._bodyRight; - case "right": - return root._bodyLeft; - default: - return root._bodyLeft + root._secondFarInset; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._bodyTop; - case "left": - return root._bodyBottom - root._secondFarInset; - case "right": - return root._bodyTop + root._secondFarInset; - default: - return root._bodyBottom; - } - } - } - - // Opposite-side connector 2 - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return root._secondFarCr; - case "right": - return -root._secondFarCr; - default: - return -root._secondFarCr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return -root._secondFarCr; - case "left": - return root._secondFarCr; - case "right": - return -root._secondFarCr; - default: - return root._secondFarCr; - } - } - radiusX: root._secondFarCr - radiusY: root._secondFarCr - direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise - } - - PathLine { - x: { - switch (root.barSide) { - case "left": - return root._secondFarCr > 0 ? root._bodyRight : root._bodyRight; - case "right": - return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft; - default: - return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft + root._secondSr; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop; - case "left": - return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._secondSr; - case "right": - return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop + root._secondSr; - default: - return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom; - } - } - } - - // Convex arc 2 - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return -root._secondSr; - case "right": - return root._secondSr; - default: - return -root._secondSr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return root._secondSr; - case "left": - return root._secondSr; - case "right": - return -root._secondSr; - default: - return -root._secondSr; - } - } - radiusX: root._secondSr - radiusY: root._secondSr - direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise - } - - // Body edge to second concave arc - PathLine { - x: { - switch (root.barSide) { - case "left": - return root._bodyLeft + root._ec; - case "right": - return root._bodyRight - root._sc; - default: - return root._bodyLeft; - } - } - y: { - switch (root.barSide) { - case "bottom": - return root._bodyBottom - root._sc; - case "left": - return root._bodyBottom; - case "right": - return root._bodyTop; - default: - return root._bodyTop + root._sc; - } - } - } - - // Concave arc 2 - PathArc { - relativeX: { - switch (root.barSide) { - case "left": - return -root._secondCr; - case "right": - return root._secondCr; - default: - return -root._secondCr; - } - } - relativeY: { - switch (root.barSide) { - case "bottom": - return root._secondCr; - case "left": - return root._secondCr; - case "right": - return -root._secondCr; - default: - return -root._secondCr; - } - } - radiusX: root._secondCr - radiusY: root._secondCr - direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise - } - } - } -} diff --git a/quickshell/Widgets/DankPopoutConnected.qml b/quickshell/Widgets/DankPopoutConnected.qml index 91e75754..624fd8f5 100644 --- a/quickshell/Widgets/DankPopoutConnected.qml +++ b/quickshell/Widgets/DankPopoutConnected.qml @@ -788,9 +788,9 @@ Item { blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome readonly property real s: Math.min(1, contentContainer.scaleValue) - readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome || Theme.isDirectionalEffect + readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome - // Directional popouts clip to the bar edge, so the blur needs to grow from + // Connected chrome clips to the bar edge, so its blur grows from // that same edge instead of translating through the bar before settling. readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0 readonly property real _dxClamp: (contentContainer.barLeft || contentContainer.barRight) ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0 diff --git a/quickshell/Widgets/DankPopoutStandalone.qml b/quickshell/Widgets/DankPopoutStandalone.qml index a5464f06..783388ae 100644 --- a/quickshell/Widgets/DankPopoutStandalone.qml +++ b/quickshell/Widgets/DankPopoutStandalone.qml @@ -589,15 +589,19 @@ Item { id: popoutBlur targetWindow: contentWindow readonly property real s: Math.min(1, contentContainer.scaleValue) - readonly property bool trackBlurFromBarEdge: root.fluidStandaloneActive readonly property real op: Math.max(0, Math.min(1, (morph.openProgress - 0.08) * 1.6)) - readonly property bool blurAlive: trackBlurFromBarEdge ? (contentContainer.revealWidth > 0 && contentContainer.revealHeight > 0) : root.shouldBeVisible + readonly property bool revealClipActive: root.fluidStandaloneActive - blurX: trackBlurFromBarEdge ? contentContainer.x + contentContainer.revealX : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) - blurY: trackBlurFromBarEdge ? contentContainer.y + contentContainer.revealY : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) - blurWidth: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealWidth : contentContainer.width * s * op) : 0 - blurHeight: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealHeight : contentContainer.height * s * op) : 0 + blurX: revealClipActive ? contentContainer.x : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) + blurY: revealClipActive ? contentContainer.y : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) + blurWidth: root.shouldBeVisible ? (revealClipActive ? contentContainer.width : contentContainer.width * s * op) : 0 + blurHeight: root.shouldBeVisible ? (revealClipActive ? contentContainer.height : contentContainer.height * s * op) : 0 blurRadius: Theme.cornerRadius + clipEnabled: revealClipActive + clipX: contentContainer.x + contentContainer.revealX + clipY: contentContainer.y + contentContainer.revealY + clipWidth: root.shouldBeVisible ? contentContainer.revealWidth : 0 + clipHeight: root.shouldBeVisible ? contentContainer.revealHeight : 0 } WlrLayershell.namespace: root.layerNamespace @@ -702,6 +706,8 @@ Item { QtObject { id: morph property real openProgress: 0 + onOpenProgressChanged: if (root.fluidStandaloneActive) + root._kickBlurCommit() Behavior on openProgress { enabled: root.animationsEnabled NumberAnimation { diff --git a/quickshell/Widgets/WindowBlur.qml b/quickshell/Widgets/WindowBlur.qml index aa8b8290..a879e649 100644 --- a/quickshell/Widgets/WindowBlur.qml +++ b/quickshell/Widgets/WindowBlur.qml @@ -16,6 +16,11 @@ Item { property real blurWidth: 0 property real blurHeight: 0 property real blurRadius: 0 + property bool clipEnabled: false + property real clipX: blurX + property real clipY: blurY + property real clipWidth: blurWidth + property real clipHeight: blurHeight readonly property bool _active: blurEnabled && BlurService.enabled && !!targetWindow @@ -26,6 +31,14 @@ Item { width: root.blurWidth height: root.blurHeight radius: root.blurRadius + + Region { + intersection: Intersection.Intersect + x: root.clipEnabled ? root.clipX : root.blurX + y: root.clipEnabled ? root.clipY : root.blurY + width: root.clipEnabled ? root.clipWidth : root.blurWidth + height: root.clipEnabled ? root.clipHeight : root.blurHeight + } } function _apply() {