From d53809cf2ba9785da1c713a1a9c8c8dba374490b Mon Sep 17 00:00:00 2001 From: purian23 Date: Fri, 12 Jun 2026 11:03:39 -0400 Subject: [PATCH] refactor(framemode): unify connected surface chrome via SDF pipeline - Shadow system rewrite with SDF quads - Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders - Improve frame blur performance - Plugin performance gate --- .../references/widget-plugin-guide.md | 2 +- quickshell/Common/ConnectedModalChrome.qml | 130 +- quickshell/Common/ConnectedModeState.qml | 211 +- .../Common/ConnectedSurfaceDescriptor.js | 159 ++ quickshell/Common/ConnectedSurfaceGeometry.js | 232 +++ quickshell/Common/ConnectedSurfaceLease.qml | 176 ++ quickshell/Common/ElevationShadow.qml | 51 +- quickshell/Common/KeyboardFocus.qml | 44 + quickshell/Common/Theme.qml | 10 + quickshell/Modals/BluetoothPairingModal.qml | 6 - .../Clipboard/ClipboardHistoryModal.qml | 7 +- quickshell/Modals/Common/DankModal.qml | 10 +- .../Modals/Common/DankModalConnected.qml | 593 +++--- .../Modals/Common/DankModalStandalone.qml | 11 +- quickshell/Modals/DankColorPickerModal.qml | 6 - .../DankLauncherV2ModalConnected.qml | 196 +- .../DankLauncherV2ModalSpotlight.qml | 51 +- .../DankLauncherV2ModalStandalone.qml | 47 +- quickshell/Modals/KeybindsModal.qml | 6 - quickshell/Modals/MuxModal.qml | 7 - quickshell/Modals/NotificationModal.qml | 6 - quickshell/Modals/PowerMenuModal.qml | 6 - .../ControlCenter/ControlCenterPopout.qml | 10 +- quickshell/Modules/DankBar/BarCanvas.qml | 3 +- quickshell/Modules/DankBar/DankBarWindow.qml | 12 +- .../DankBar/Popouts/SystemUpdatePopout.qml | 10 +- .../Modules/DankBar/Widgets/SystemTrayBar.qml | 120 +- .../Modules/DankDash/MediaDropdownOverlay.qml | 6 +- quickshell/Modules/Dock/Dock.qml | 71 +- quickshell/Modules/Frame/FrameBorder.qml | 41 +- quickshell/Modules/Frame/FrameWindow.qml | 1787 ++++++----------- .../Center/HistoryNotificationCard.qml | 2 +- .../Notifications/Center/NotificationCard.qml | 2 +- .../Notifications/Popup/NotificationPopup.qml | 22 +- .../Popup/NotificationPopupManager.qml | 21 +- .../Modules/Plugins/PluginComponent.qml | 8 +- quickshell/Shaders/frag/connected_arc.frag | 110 + quickshell/Shaders/frag/connected_chrome.frag | 63 + quickshell/Shaders/frag/elevation_rect.frag | 54 + quickshell/Shaders/frag/frame_arc.frag | 42 + quickshell/Shaders/qsb/connected_arc.frag.qsb | Bin 0 -> 7880 bytes .../Shaders/qsb/connected_chrome.frag.qsb | Bin 0 -> 4833 bytes .../Shaders/qsb/elevation_rect.frag.qsb | Bin 0 -> 4215 bytes quickshell/Shaders/qsb/frame_arc.frag.qsb | Bin 0 -> 3124 bytes quickshell/Widgets/ConnectedCorner.qml | 151 -- quickshell/Widgets/ConnectedShape.qml | 414 ---- quickshell/Widgets/DankOSD.qml | 2 - quickshell/Widgets/DankPopout.qml | 17 +- quickshell/Widgets/DankPopoutConnected.qml | 345 ++-- quickshell/Widgets/DankPopoutStandalone.qml | 69 +- quickshell/Widgets/DismissZone.qml | 25 + quickshell/Widgets/WindowBlur.qml | 18 +- 52 files changed, 2588 insertions(+), 2804 deletions(-) create mode 100644 quickshell/Common/ConnectedSurfaceDescriptor.js create mode 100644 quickshell/Common/ConnectedSurfaceGeometry.js create mode 100644 quickshell/Common/ConnectedSurfaceLease.qml create mode 100644 quickshell/Common/KeyboardFocus.qml create mode 100644 quickshell/Shaders/frag/connected_arc.frag create mode 100644 quickshell/Shaders/frag/connected_chrome.frag create mode 100644 quickshell/Shaders/frag/elevation_rect.frag create mode 100644 quickshell/Shaders/frag/frame_arc.frag create mode 100644 quickshell/Shaders/qsb/connected_arc.frag.qsb create mode 100644 quickshell/Shaders/qsb/connected_chrome.frag.qsb create mode 100644 quickshell/Shaders/qsb/elevation_rect.frag.qsb create mode 100644 quickshell/Shaders/qsb/frame_arc.frag.qsb delete mode 100644 quickshell/Widgets/ConnectedCorner.qml delete mode 100644 quickshell/Widgets/ConnectedShape.qml create mode 100644 quickshell/Widgets/DismissZone.qml diff --git a/.agents/skills/dms-plugin-dev/references/widget-plugin-guide.md b/.agents/skills/dms-plugin-dev/references/widget-plugin-guide.md index 05f752c3..c8cea69d 100644 --- a/.agents/skills/dms-plugin-dev/references/widget-plugin-guide.md +++ b/.agents/skills/dms-plugin-dev/references/widget-plugin-guide.md @@ -235,7 +235,7 @@ Conditionally show/hide the bar pill: ```qml PluginComponent { visibilityCommand: "pgrep -x myapp" - visibilityInterval: 5000 // check every 5 seconds + visibilityInterval: 5 // seconds between checks; polling pauses while the bar is hidden } ``` diff --git a/quickshell/Common/ConnectedModalChrome.qml b/quickshell/Common/ConnectedModalChrome.qml index e11ef30a..5ab2b750 100644 --- a/quickshell/Common/ConnectedModalChrome.qml +++ b/quickshell/Common/ConnectedModalChrome.qml @@ -7,6 +7,7 @@ Item { required property var modalHandle required property string claimPrefix + property string surfaceKind: "modal" property string screenName: "" property bool enabled: false property bool active: false @@ -14,112 +15,97 @@ Item { property bool dockBlocked: false property string dockSide: "" - property string claimId: "" - property string claimedScreenName: "" + property alias claimId: lease.claimId + property alias claimedScreenName: lease.claimedScreenName signal recoveryRequested visible: false - function _nextClaimId() { - return claimPrefix + ":" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000); - } - function _isCurrentModal(name) { return !!name && ModalManager.isCurrentModal(modalHandle, name); } - function _shouldRecover() { - return active && enabled && _isCurrentModal(screenName); - } - - function _requestRecovery() { - if (_shouldRecover()) - recoveryRequested(); + ConnectedSurfaceLease { + id: lease + claimPrefix: root.claimPrefix + screenName: root.screenName + enabled: root.enabled + active: root.active + presented: root.presented + dockBlocked: root.dockBlocked + dockSide: root.dockSide + isCurrentOwner: function(name) { + return root._isCurrentModal(name); + } + hasOwner: function(name, ownerId) { + return ConnectedModeState.hasModalOwner(name, ownerId); + } + statePresent: function(name, ownerId) { + return ConnectedModeState.hasModalOwner(name, ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, root.surfaceKind, ownerId); + } + claimState: function(name, state, ownerId) { + return ConnectedModeState.claimModalState(name, state, ownerId); + } + ensureState: function(name, state, ownerId) { + return ConnectedModeState.ensureModalState(name, state, ownerId); + } + releaseState: function(name, ownerId) { + return ConnectedModeState.clearModalState(name, ownerId); + } + updateAnimationState: function(name, ownerId, animX, animY) { + return ConnectedModeState.setModalAnim(name, animX, animY, ownerId); + } + updateBodyState: function(name, ownerId, bodyX, bodyY, bodyW, bodyH) { + return ConnectedModeState.setModalBody(name, bodyX, bodyY, bodyW, bodyH, ownerId); + } + requestDockRetract: function(ownerId, name, side) { + return ConnectedModeState.requestDockRetract(ownerId, name, side); + } + releaseDockRetract: function(ownerId) { + return ConnectedModeState.releaseDockRetract(ownerId); + } + onRecoveryRequested: root.recoveryRequested() } function publish(state) { - if (!enabled || !screenName || !state) { - release(); - return false; - } - if (claimedScreenName && claimedScreenName !== screenName) - release(); - - const isCurrent = _isCurrentModal(screenName); - let isClaim = !claimId; - if (isClaim && !isCurrent) - return false; - if (isClaim) - claimId = _nextClaimId(); - - let published = isClaim ? ConnectedModeState.claimModalState(screenName, state, claimId) : ConnectedModeState.ensureModalState(screenName, state, claimId); - if (!published && !isClaim && isCurrent) { - ConnectedModeState.releaseDockRetract(claimId); - claimId = _nextClaimId(); - published = ConnectedModeState.claimModalState(screenName, state, claimId); - } - if (!published) - return false; - - claimedScreenName = screenName; - if (dockBlocked && presented) - ConnectedModeState.requestDockRetract(claimId, screenName, dockSide); - else - ConnectedModeState.releaseDockRetract(claimId); - return true; + return lease.publish(Object.assign({}, state, { + "kind": root.surfaceKind, + "screenName": root.screenName, + "presented": root.presented, + "dockRetractSide": root.dockBlocked ? root.dockSide : "" + }), false); } function updateAnim(animX, animY) { - if (!enabled || !claimId || !claimedScreenName) - return false; - if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) { - _requestRecovery(); - return false; - } - return ConnectedModeState.setModalAnim(claimedScreenName, animX, animY, claimId); + return lease.updateAnim(animX, animY); } function updateBody(bodyX, bodyY, bodyW, bodyH) { - if (!enabled || !claimId || !claimedScreenName) - return false; - if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) { - _requestRecovery(); - return false; - } - return ConnectedModeState.setModalBody(claimedScreenName, bodyX, bodyY, bodyW, bodyH, claimId); + return lease.updateBody(bodyX, bodyY, bodyW, bodyH); } function release() { - if (!claimId) - return; - ConnectedModeState.releaseDockRetract(claimId); - const releasedClaimId = claimId; - const releasedScreenName = claimedScreenName; - claimId = ""; - claimedScreenName = ""; - if (releasedScreenName) - ConnectedModeState.clearModalState(releasedScreenName, releasedClaimId); + return lease.release(); } - Component.onDestruction: release() - Connections { target: ModalManager function onModalChanged() { - root._requestRecovery(); + lease.requestRecovery(); } } Connections { target: ConnectedModeState function onModalOwnersChanged() { - if (!ConnectedModeState.hasModalOwner(root.screenName, root.claimId)) - root._requestRecovery(); + lease.checkOwnershipRecovery(); } function onModalStatesChanged() { - if (!ConnectedModeState.modalStates[root.screenName]) - root._requestRecovery(); + lease.checkStateRecovery(); + } + function onSurfaceDescriptorsChanged() { + lease.checkStateRecovery(); } } } diff --git a/quickshell/Common/ConnectedModeState.qml b/quickshell/Common/ConnectedModeState.qml index 80e5353b..1ef80f82 100644 --- a/quickshell/Common/ConnectedModeState.qml +++ b/quickshell/Common/ConnectedModeState.qml @@ -3,10 +3,123 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell +import "ConnectedSurfaceDescriptor.js" as SurfaceDescriptor Singleton { id: root + property var surfaceDescriptors: ({}) + + function _surfaceSlot(kind) { + return SurfaceDescriptor.slotForKind(kind); + } + + function surfaceDescriptor(screenName, kind) { + const slot = _surfaceSlot(kind); + const screenDescriptors = screenName ? surfaceDescriptors[screenName] : null; + const descriptor = screenDescriptors && screenDescriptors[slot] ? screenDescriptors[slot] : SurfaceDescriptor.empty(kind, screenName); + let bodyRect = descriptor.bodyRect; + let animationOffset = descriptor.animationOffset; + if (slot === "popout" && popoutScreen === screenName) { + bodyRect = { + "x": popoutBodyX, + "y": popoutBodyY, + "width": popoutBodyW, + "height": popoutBodyH + }; + animationOffset = { + "x": popoutAnimX, + "y": popoutAnimY + }; + } else if (slot === "modal" && modalStates[screenName]) { + const modal = modalStates[screenName]; + bodyRect = { + "x": modal.bodyX, + "y": modal.bodyY, + "width": modal.bodyW, + "height": modal.bodyH + }; + animationOffset = { + "x": modal.animX, + "y": modal.animY + }; + } else if (slot === "dock" && dockStates[screenName]) { + const dock = dockStates[screenName]; + const slide = dockSlides[screenName] || { + "x": dock.slideX, + "y": dock.slideY + }; + bodyRect = { + "x": dock.bodyX, + "y": dock.bodyY, + "width": dock.bodyW, + "height": dock.bodyH + }; + animationOffset = { + "x": slide.x, + "y": slide.y + }; + } else if (slot === "notification" && notificationStates[screenName]) { + const notification = notificationStates[screenName]; + bodyRect = { + "x": notification.bodyX, + "y": notification.bodyY, + "width": notification.bodyW, + "height": notification.bodyH + }; + } + return SurfaceDescriptor.normalize({ + "bodyRect": bodyRect, + "animationOffset": animationOffset + }, descriptor); + } + + function hasSurfaceDescriptor(screenName, kind, ownerId) { + const descriptor = surfaceDescriptor(screenName, kind); + return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId); + } + + function _setSurfaceDescriptor(screenName, slotKind, state, ownerId) { + if (!screenName || !state) + return false; + const slot = _surfaceSlot(slotKind); + const currentScreen = surfaceDescriptors[screenName] || {}; + const previous = currentScreen[slot] || SurfaceDescriptor.empty(state.kind || slotKind, screenName); + let normalized = SurfaceDescriptor.normalize(Object.assign({}, state, { + "ownerId": ownerId !== undefined ? ownerId : previous.ownerId, + "screenName": screenName, + "revision": previous.revision + }), previous); + if (SurfaceDescriptor.same(previous, normalized)) + return true; + normalized = SurfaceDescriptor.withRevision(normalized, previous.revision + 1); + const nextScreen = _cloneDict(currentScreen); + nextScreen[slot] = normalized; + const next = _cloneDict(surfaceDescriptors); + next[screenName] = nextScreen; + surfaceDescriptors = next; + return true; + } + + function _clearSurfaceDescriptor(screenName, kind, ownerId) { + if (!screenName) + return false; + const slot = _surfaceSlot(kind); + const currentScreen = surfaceDescriptors[screenName]; + const current = currentScreen ? currentScreen[slot] : null; + if (!current || (ownerId && current.ownerId !== ownerId)) + return false; + const nextScreen = _cloneDict(currentScreen); + delete nextScreen[slot]; + const next = _cloneDict(surfaceDescriptors); + if (Object.keys(nextScreen).length > 0) + next[screenName] = nextScreen; + else + delete next[screenName]; + surfaceDescriptors = next; + return true; + } + readonly property var emptyDockState: ({ "reveal": false, "barSide": "bottom", @@ -18,7 +131,6 @@ Singleton { "slideY": 0 }) - // Popout state (updated by DankPopout when connectedFrameModeActive) property string popoutOwnerId: "" property bool popoutVisible: false property string popoutBarSide: "top" @@ -32,14 +144,10 @@ Singleton { property bool popoutOmitStartConnector: false property bool popoutOmitEndConnector: false - // Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name property var dockStates: ({}) - // Dock slide offsets — hot-path updates separated from full geometry state property var dockSlides: ({}) - // Surfaces are keyed by screen.name. FrameWindow watches to refresh connected chrome - // after claim/release boundaries without tracking each animation frame property var surfaceRevisions: ({}) function _cloneDict(src) { @@ -69,8 +177,10 @@ Singleton { popoutOwnerId = claimId; const ok = updatePopout(claimId, state); if (ok) { - if (previousScreen && previousScreen !== popoutScreen) + if (previousScreen && previousScreen !== popoutScreen) { + _clearSurfaceDescriptor(previousScreen, "popout"); _bumpSurfaceRevision(previousScreen); + } _bumpSurfaceRevision(popoutScreen); } return ok; @@ -103,6 +213,21 @@ Singleton { if (state.omitEndConnector !== undefined) popoutOmitEndConnector = !!state.omitEndConnector; + _setSurfaceDescriptor(popoutScreen, "popout", Object.assign({}, state, { + "kind": "popout", + "screenName": popoutScreen, + "visible": popoutVisible, + "presented": state.presented !== undefined ? !!state.presented : popoutVisible, + "barSide": popoutBarSide, + "bodyX": popoutBodyX, + "bodyY": popoutBodyY, + "bodyW": popoutBodyW, + "bodyH": popoutBodyH, + "animX": popoutAnimX, + "animY": popoutAnimY, + "omitStartConnector": popoutOmitStartConnector, + "omitEndConnector": popoutOmitEndConnector + }), claimId); return true; } @@ -123,6 +248,7 @@ Singleton { popoutScreen = ""; popoutOmitStartConnector = false; popoutOmitEndConnector = false; + _clearSurfaceDescriptor(releasedScreen, "popout", claimId); _bumpSurfaceRevision(releasedScreen); return true; } @@ -193,13 +319,21 @@ Singleton { return false; const normalized = _normalizeDockState(state); - if (_sameDockState(dockStates[screenName], normalized)) - return true; + const descriptorState = Object.assign({}, state, normalized, { + "kind": "dock", + "screenName": screenName, + "visible": normalized.reveal, + "presented": normalized.reveal, + "phase": normalized.reveal ? (state.phase || "open") : "hidden" + }); const previous = dockStates[screenName] || emptyDockState; - - const next = _cloneDict(dockStates); - next[screenName] = normalized; - dockStates = next; + const stateChanged = !_sameDockState(dockStates[screenName], normalized); + if (stateChanged) { + const next = _cloneDict(dockStates); + next[screenName] = normalized; + dockStates = next; + } + _setSurfaceDescriptor(screenName, "dock", descriptorState, "dock:" + screenName); if (!!previous.reveal !== !!normalized.reveal) _bumpSurfaceRevision(screenName); return true; @@ -212,8 +346,8 @@ Singleton { const next = _cloneDict(dockStates); delete next[screenName]; dockStates = next; + _clearSurfaceDescriptor(screenName, "dock"); - // Also clear corresponding slide if (dockSlides[screenName]) { const nextSlides = _cloneDict(dockSlides); delete nextSlides[screenName]; @@ -283,13 +417,20 @@ Singleton { return false; const normalized = _normalizeNotificationState(state); - if (_sameNotificationState(notificationStates[screenName], normalized)) - return true; + const descriptorState = Object.assign({}, state, normalized, { + "kind": "notification", + "screenName": screenName, + "presented": normalized.visible, + "phase": normalized.visible ? (state.phase || "open") : "hidden" + }); const previous = notificationStates[screenName] || emptyNotificationState; - - const next = _cloneDict(notificationStates); - next[screenName] = normalized; - notificationStates = next; + const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized); + if (stateChanged) { + const next = _cloneDict(notificationStates); + next[screenName] = normalized; + notificationStates = next; + } + _setSurfaceDescriptor(screenName, "notification", descriptorState, "notification:" + screenName); if (!!previous.visible !== !!normalized.visible) _bumpSurfaceRevision(screenName); return true; @@ -302,11 +443,11 @@ Singleton { const next = _cloneDict(notificationStates); delete next[screenName]; notificationStates = next; + _clearSurfaceDescriptor(screenName, "notification"); _bumpSurfaceRevision(screenName); return true; } - // DankModal / DankLauncherV2Modal State readonly property var emptyModalState: ({ "visible": false, "barSide": "bottom", @@ -362,6 +503,10 @@ Singleton { const next = _cloneDict(modalStates); next[screenName] = normalized; modalStates = next; + _setSurfaceDescriptor(screenName, "modal", Object.assign({}, state, normalized, { + "kind": state.kind || "modal", + "screenName": screenName + }), ownerId || ""); _bumpSurfaceRevision(screenName); return true; } @@ -372,11 +517,16 @@ Singleton { if (ownerId && modalOwners[screenName] !== ownerId) return false; const normalized = _normalizeModalState(state); - if (_sameModalState(modalStates[screenName], normalized)) - return true; - const next = _cloneDict(modalStates); - next[screenName] = normalized; - modalStates = next; + const descriptorState = Object.assign({}, state, normalized, { + "kind": state.kind || (surfaceDescriptor(screenName, "modal").kind || "modal"), + "screenName": screenName + }); + if (!_sameModalState(modalStates[screenName], normalized)) { + const next = _cloneDict(modalStates); + next[screenName] = normalized; + modalStates = next; + } + _setSurfaceDescriptor(screenName, "modal", descriptorState, ownerId || modalOwners[screenName] || ""); return true; } @@ -395,10 +545,6 @@ Singleton { return updateModalState(screenName, state, ownerId); } - function setModalState(screenName, state) { - return updateModalState(screenName, state, null); - } - function clearModalState(screenName, ownerId) { if (!screenName) return false; @@ -418,6 +564,7 @@ Singleton { delete nextOwners[screenName]; modalOwners = nextOwners; } + _clearSurfaceDescriptor(screenName, "modal", ownerId); _bumpSurfaceRevision(screenName); return true; } @@ -501,9 +648,6 @@ Singleton { return false; } - // Prune state for screens that are no longer connected. Stale entries - // accumulate across hotplug cycles otherwise — Frame's per-screen - // FrameInstance doesn't notice when its peer dicts go orphan. function _pruneToLiveScreens() { const live = {}; const screens = Quickshell.screens || []; @@ -543,6 +687,9 @@ Singleton { const nextSurfaceRevisions = pruneKeyed(surfaceRevisions); if (nextSurfaceRevisions !== null) surfaceRevisions = nextSurfaceRevisions; + const nextDescriptors = pruneKeyed(surfaceDescriptors); + if (nextDescriptors !== null) + surfaceDescriptors = nextDescriptors; let retractChanged = false; const nextRetract = {}; diff --git a/quickshell/Common/ConnectedSurfaceDescriptor.js b/quickshell/Common/ConnectedSurfaceDescriptor.js new file mode 100644 index 00000000..203e1eb0 --- /dev/null +++ b/quickshell/Common/ConnectedSurfaceDescriptor.js @@ -0,0 +1,159 @@ +.pragma library + +var VALID_KINDS = { + "popout": true, + "modal": true, + "launcher": true, + "dock": true, + "notification": true +}; + +var VALID_PHASES = { + "opening": true, + "open": true, + "closing": true, + "hidden": true, + "recovering": true +}; + +function _number(value, fallback) { + var n = Number(value); + return isNaN(n) ? fallback : n; +} + +function _bool(value, fallback) { + return value === undefined ? fallback : !!value; +} + +function _kind(value, fallback) { + if (VALID_KINDS[value]) + return value; + return VALID_KINDS[fallback] ? fallback : "modal"; +} + +function _defaultBarSide(kind) { + return kind === "popout" || kind === "notification" ? "top" : "bottom"; +} + +function _barSide(value, fallback) { + if (value === "top" || value === "bottom" || value === "left" || value === "right") + return value; + return fallback; +} + +function slotForKind(kind) { + return kind === "launcher" ? "modal" : _kind(kind, "modal"); +} + +function inferPhase(visible, presented, requestedPhase) { + if (VALID_PHASES[requestedPhase]) + return requestedPhase; + if (!visible && !presented) + return "hidden"; + if (!visible && presented) + return "closing"; + return "open"; +} + +function normalize(input, defaults) { + var source = input || {}; + var base = defaults || {}; + var kind = _kind(source.kind, base.kind); + var defaultSide = _defaultBarSide(kind); + var sourceRect = source.bodyRect || {}; + var baseRect = base.bodyRect || {}; + var sourceOffset = source.animationOffset || {}; + var baseOffset = base.animationOffset || {}; + var visible = _bool(source.visible !== undefined ? source.visible : source.reveal, _bool(base.visible !== undefined ? base.visible : base.reveal, false)); + var presented = _bool(source.presented, _bool(base.presented, visible)); + var bodyRect = { + "x": _number(sourceRect.x !== undefined ? sourceRect.x : source.bodyX, _number(baseRect.x !== undefined ? baseRect.x : base.bodyX, 0)), + "y": _number(sourceRect.y !== undefined ? sourceRect.y : source.bodyY, _number(baseRect.y !== undefined ? baseRect.y : base.bodyY, 0)), + "width": Math.max(0, _number(sourceRect.width !== undefined ? sourceRect.width : source.bodyW, _number(baseRect.width !== undefined ? baseRect.width : base.bodyW, 0))), + "height": Math.max(0, _number(sourceRect.height !== undefined ? sourceRect.height : source.bodyH, _number(baseRect.height !== undefined ? baseRect.height : base.bodyH, 0))) + }; + var animationOffset = { + "x": _number(sourceOffset.x !== undefined ? sourceOffset.x : (source.animX !== undefined ? source.animX : source.slideX), _number(baseOffset.x !== undefined ? baseOffset.x : (base.animX !== undefined ? base.animX : base.slideX), 0)), + "y": _number(sourceOffset.y !== undefined ? sourceOffset.y : (source.animY !== undefined ? source.animY : source.slideY), _number(baseOffset.y !== undefined ? baseOffset.y : (base.animY !== undefined ? base.animY : base.slideY), 0)) + }; + var screenName = source.screenName !== undefined ? source.screenName : (source.screen !== undefined ? source.screen : (base.screenName !== undefined ? base.screenName : base.screen)); + var opacity = Math.max(0, Math.min(1, _number(source.opacity, _number(base.opacity, 1)))); + + return { + "ownerId": String(source.ownerId !== undefined ? source.ownerId : (base.ownerId || "")), + "kind": kind, + "screenName": String(screenName || ""), + "phase": inferPhase(visible, presented, source.phase !== undefined ? source.phase : base.phase), + "visible": visible, + "presented": presented, + "barSide": _barSide(source.barSide, _barSide(base.barSide, defaultSide)), + "bodyRect": bodyRect, + "animationOffset": animationOffset, + "scale": Math.max(0, _number(source.scale, _number(base.scale, 1))), + "opacity": opacity, + "omitStartConnector": _bool(source.omitStartConnector, _bool(base.omitStartConnector, false)), + "omitEndConnector": _bool(source.omitEndConnector, _bool(base.omitEndConnector, false)), + "dockRetractSide": String(source.dockRetractSide !== undefined ? source.dockRetractSide : (base.dockRetractSide || "")), + "revision": Math.max(0, Math.floor(_number(source.revision, _number(base.revision, 0)))) + }; +} + +function empty(kind, screenName) { + return normalize({ + "kind": kind, + "screenName": screenName || "", + "phase": "hidden", + "visible": false, + "presented": false + }); +} + +function withRevision(descriptor, revision) { + var next = normalize(descriptor); + next.revision = Math.max(0, Math.floor(_number(revision, next.revision))); + return next; +} + +function withAnimationOffset(descriptor, x, y) { + var next = normalize(descriptor); + next.animationOffset = { + "x": x === undefined ? next.animationOffset.x : _number(x, next.animationOffset.x), + "y": y === undefined ? next.animationOffset.y : _number(y, next.animationOffset.y) + }; + return next; +} + +function withBodyRect(descriptor, x, y, width, height) { + var next = normalize(descriptor); + next.bodyRect = { + "x": x === undefined ? next.bodyRect.x : _number(x, next.bodyRect.x), + "y": y === undefined ? next.bodyRect.y : _number(y, next.bodyRect.y), + "width": width === undefined ? next.bodyRect.width : Math.max(0, _number(width, next.bodyRect.width)), + "height": height === undefined ? next.bodyRect.height : Math.max(0, _number(height, next.bodyRect.height)) + }; + return next; +} + +function same(a, b, threshold) { + if (!a || !b) + return false; + var epsilon = threshold === undefined ? 0.5 : Math.max(0, Number(threshold)); + return a.ownerId === b.ownerId + && a.kind === b.kind + && a.screenName === b.screenName + && a.phase === b.phase + && a.visible === b.visible + && a.presented === b.presented + && a.barSide === b.barSide + && Math.abs(a.bodyRect.x - b.bodyRect.x) < epsilon + && Math.abs(a.bodyRect.y - b.bodyRect.y) < epsilon + && Math.abs(a.bodyRect.width - b.bodyRect.width) < epsilon + && Math.abs(a.bodyRect.height - b.bodyRect.height) < epsilon + && Math.abs(a.animationOffset.x - b.animationOffset.x) < epsilon + && Math.abs(a.animationOffset.y - b.animationOffset.y) < epsilon + && Math.abs(a.scale - b.scale) < 0.0001 + && Math.abs(a.opacity - b.opacity) < 0.0001 + && a.omitStartConnector === b.omitStartConnector + && a.omitEndConnector === b.omitEndConnector + && a.dockRetractSide === b.dockRetractSide; +} diff --git a/quickshell/Common/ConnectedSurfaceGeometry.js b/quickshell/Common/ConnectedSurfaceGeometry.js new file mode 100644 index 00000000..fcdf30f0 --- /dev/null +++ b/quickshell/Common/ConnectedSurfaceGeometry.js @@ -0,0 +1,232 @@ +.pragma library + +function _number(value, fallback) { + var n = Number(value); + return isNaN(n) ? fallback : n; +} + +function snap(value, dpr) { + var scale = dpr || 1; + return Math.round(_number(value, 0) * scale) / scale; +} + +function isHorizontal(side) { + return side === "top" || side === "bottom"; +} + +function isVertical(side) { + return side === "left" || side === "right"; +} + +function bodyRect(descriptor, dpr) { + var source = descriptor && descriptor.bodyRect ? descriptor.bodyRect : descriptor || {}; + return { + "x": snap(source.x !== undefined ? source.x : source.bodyX, dpr), + "y": snap(source.y !== undefined ? source.y : source.bodyY, dpr), + "width": Math.max(0, snap(source.width !== undefined ? source.width : source.bodyW, dpr)), + "height": Math.max(0, snap(source.height !== undefined ? source.height : source.bodyH, dpr)) + }; +} + +function animatedBodyRect(descriptor, dpr) { + var rect = bodyRect(descriptor, dpr); + var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : descriptor || {}; + var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom"; + var dx = isVertical(side) ? Math.max(-rect.width, Math.min(_number(offset.x !== undefined ? offset.x : offset.animX, 0), rect.width)) : 0; + var dy = isHorizontal(side) ? Math.max(-rect.height, Math.min(_number(offset.y !== undefined ? offset.y : offset.animY, 0), rect.height)) : 0; + + return { + "x": snap(rect.x + (side === "right" ? dx : 0), dpr), + "y": snap(rect.y + (side === "bottom" ? dy : 0), dpr), + "width": Math.max(0, snap(rect.width - Math.abs(dx), dpr)), + "height": Math.max(0, snap(rect.height - Math.abs(dy), dpr)), + "dx": snap(dx, dpr), + "dy": snap(dy, dpr) + }; +} + +function translatedBodyRect(descriptor, dpr) { + var rect = bodyRect(descriptor, dpr); + var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : {}; + return { + "x": snap(rect.x + _number(offset.x, 0), dpr), + "y": snap(rect.y + _number(offset.y, 0), dpr), + "width": rect.width, + "height": rect.height + }; +} + +function connectorRadii(descriptor, rect, connectedRadius, surfaceRadius, dpr, nearIncludesSurface) { + var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom"; + var horizontal = isHorizontal(side); + var extent = horizontal ? rect.height : rect.width; + var crossSize = horizontal ? rect.width : rect.height; + var nearLimit = nearIncludesSurface ? Math.min(connectedRadius, surfaceRadius, extent, crossSize / 2) : Math.min(connectedRadius, extent, crossSize / 2); + var farLimit = Math.min(connectedRadius, surfaceRadius, crossSize / 2); + var near = snap(Math.max(0, nearLimit), dpr); + var far = snap(Math.max(0, farLimit), dpr); + var omitStart = !!(descriptor && descriptor.omitStartConnector); + var omitEnd = !!(descriptor && descriptor.omitEndConnector); + return { + "near": near, + "far": far, + "start": omitStart ? 0 : near, + "end": omitEnd ? 0 : near, + "farStart": omitStart ? far : 0, + "farEnd": omitEnd ? far : 0, + "farExtent": Math.max(omitStart ? far : 0, omitEnd ? far : 0) + }; +} + +function _connectorWidth(side, spacing, radius) { + return isVertical(side) ? spacing + radius : radius; +} + +function _connectorHeight(side, spacing, radius) { + return isVertical(side) ? radius : spacing + radius; +} + +function connectorRect(side, rect, placement, spacing, radius, dpr) { + var width = _connectorWidth(side, spacing, radius); + var height = _connectorHeight(side, spacing, radius); + var seamX = isVertical(side) ? (side === "left" ? rect.x : rect.x + rect.width) : (placement === "left" ? rect.x : rect.x + rect.width); + var seamY = side === "top" ? rect.y : (side === "bottom" ? rect.y + rect.height : (placement === "left" ? rect.y : rect.y + rect.height)); + var x = isVertical(side) ? (side === "left" ? seamX : seamX - width) : (placement === "left" ? seamX - width : seamX); + var y = side === "top" ? seamY : (side === "bottom" ? seamY - height : (placement === "left" ? seamY - height : seamY)); + return { + "x": snap(x, dpr), + "y": snap(y, dpr), + "width": Math.max(0, snap(width, dpr)), + "height": Math.max(0, snap(height, dpr)) + }; +} + +function farConnectorRect(side, rect, placement, radius, dpr) { + var x; + var y; + if (isHorizontal(side)) { + x = placement === "left" ? rect.x : rect.x + rect.width - radius; + y = side === "top" ? rect.y + rect.height : rect.y - radius; + } else { + x = side === "left" ? rect.x + rect.width : rect.x - radius; + y = placement === "left" ? rect.y : rect.y + rect.height - radius; + } + return { + "x": snap(x, dpr), + "y": snap(y, dpr), + "width": Math.max(0, snap(radius, dpr)), + "height": Math.max(0, snap(radius, dpr)) + }; +} + +function farBodyCapRect(side, rect, placement, radius, dpr) { + var x; + var y; + if (isHorizontal(side)) { + x = placement === "left" ? rect.x : rect.x + rect.width - radius; + y = side === "top" ? rect.y + rect.height - radius : rect.y; + } else { + x = side === "left" ? rect.x + rect.width - radius : rect.x; + y = placement === "left" ? rect.y : rect.y + rect.height - radius; + } + return { + "x": snap(x, dpr), + "y": snap(y, dpr), + "width": Math.max(0, snap(radius, dpr)), + "height": Math.max(0, snap(radius, dpr)) + }; +} + +function chromeBounds(rect, side, startRadius, endRadius, farExtent, dpr) { + var horizontal = isHorizontal(side); + var bodyOffsetX = horizontal ? startRadius : (side === "right" ? farExtent : 0); + var bodyOffsetY = horizontal ? (side === "bottom" ? farExtent : 0) : startRadius; + return { + "x": snap(rect.x - bodyOffsetX, dpr), + "y": snap(rect.y - bodyOffsetY, dpr), + "width": Math.max(0, snap(horizontal ? rect.width + startRadius + endRadius : rect.width + farExtent, dpr)), + "height": Math.max(0, snap(horizontal ? rect.height + farExtent : rect.height + startRadius + endRadius, dpr)), + "bodyOffsetX": snap(bodyOffsetX, dpr), + "bodyOffsetY": snap(bodyOffsetY, dpr) + }; +} + +function fillBounds(rect, side, seamOverlap, dpr) { + var overlapX = isHorizontal(side) ? seamOverlap : 0; + var overlapY = isVertical(side) ? seamOverlap : 0; + return { + "x": snap(rect.x - overlapX, dpr), + "y": snap(rect.y - overlapY, dpr), + "width": Math.max(0, snap(rect.width + overlapX * 2, dpr)), + "height": Math.max(0, snap(rect.height + overlapY * 2, dpr)) + }; +} + +function clipEnvelope(rect, side, radii, seamOverlap, dpr) { + var fill = fillBounds(rect, side, seamOverlap, dpr); + var chrome = chromeBounds(fill, side, radii.start, radii.end, radii.farExtent, dpr); + return { + "x": chrome.x, + "y": chrome.y, + "width": chrome.width, + "height": chrome.height, + "bodyX": snap(fill.x - chrome.x, dpr), + "bodyY": snap(fill.y - chrome.y, dpr), + "bodyWidth": fill.width, + "bodyHeight": fill.height + }; +} + +function blurRegions(descriptor, rect, radii, dpr) { + var side = descriptor.barSide; + var regions = [bodyRect(rect, dpr)]; + if (radii.start > 0) + regions.push(connectorRect(side, rect, "left", 0, radii.start, dpr)); + if (radii.end > 0) + regions.push(connectorRect(side, rect, "right", 0, radii.end, dpr)); + if (radii.farStart > 0) { + regions.push(farConnectorRect(side, rect, "left", radii.farStart, dpr)); + regions.push(farBodyCapRect(side, rect, "left", radii.farStart, dpr)); + } + if (radii.farEnd > 0) { + regions.push(farConnectorRect(side, rect, "right", radii.farEnd, dpr)); + regions.push(farBodyCapRect(side, rect, "right", radii.farEnd, dpr)); + } + return regions; +} + +function unionBounds(rects, padding, dpr) { + var minX = Infinity; + var minY = Infinity; + var maxX = -Infinity; + var maxY = -Infinity; + for (var i = 0; i < rects.length; i++) { + var rect = rects[i]; + if (!rect || rect.width <= 0 || rect.height <= 0) + continue; + minX = Math.min(minX, rect.x); + minY = Math.min(minY, rect.y); + maxX = Math.max(maxX, rect.x + rect.width); + maxY = Math.max(maxY, rect.y + rect.height); + } + if (minX === Infinity) + return {"x": 0, "y": 0, "width": 0, "height": 0}; + var pad = Math.max(0, _number(padding, 0)); + return { + "x": snap(minX - pad, dpr), + "y": snap(minY - pad, dpr), + "width": Math.max(0, snap(maxX - minX + pad * 2, dpr)), + "height": Math.max(0, snap(maxY - minY + pad * 2, dpr)) + }; +} + +function shadowSourceBounds(descriptor, rect, radii, padding, dpr) { + return unionBounds(blurRegions(descriptor, rect, radii, dpr), padding, dpr); +} + +function stableEqual(a, b, dpr) { + if (!a || !b) + return false; + var threshold = 0.5 / (dpr || 1); + return Math.abs(a.x - b.x) < threshold && Math.abs(a.y - b.y) < threshold && Math.abs(a.width - b.width) < threshold && Math.abs(a.height - b.height) < threshold; +} diff --git a/quickshell/Common/ConnectedSurfaceLease.qml b/quickshell/Common/ConnectedSurfaceLease.qml new file mode 100644 index 00000000..f5a7a97c --- /dev/null +++ b/quickshell/Common/ConnectedSurfaceLease.qml @@ -0,0 +1,176 @@ +pragma ComponentBehavior: Bound + +import QtQuick + +Item { + id: root + + required property string claimPrefix + required property var isCurrentOwner + required property var hasOwner + required property var claimState + required property var ensureState + required property var releaseState + + property var statePresent: null + property var updateAnimationState: null + property var updateBodyState: null + property var requestDockRetract: null + property var releaseDockRetract: null + + property string screenName: "" + property bool enabled: false + property bool active: false + property bool presented: false + property bool dockBlocked: false + property string dockSide: "" + property bool renewTokenOnRecovery: true + + property string claimId: "" + property string claimedScreenName: "" + property int _claimSerial: 0 + + signal recoveryRequested + + visible: false + + function _nextClaimId() { + _claimSerial += 1; + return claimPrefix + ":" + (new Date()).getTime() + ":" + _claimSerial + ":" + Math.floor(Math.random() * 1000000); + } + + function _isCurrent(name) { + return !!name && !!isCurrentOwner && !!isCurrentOwner(name); + } + + function _hasOwner(name, ownerId) { + return !!name && !!ownerId && !!hasOwner && !!hasOwner(name, ownerId); + } + + function _hasState(name, ownerId) { + return !statePresent || !!statePresent(name, ownerId); + } + + function _shouldRecover() { + return active && enabled && _isCurrent(screenName); + } + + function requestRecovery() { + if (!_shouldRecover()) + return false; + recoveryRequested(); + return true; + } + + function checkOwnershipRecovery() { + if (!_shouldRecover()) + return false; + if (claimedScreenName === screenName && _hasOwner(screenName, claimId)) + return false; + recoveryRequested(); + return true; + } + + function checkStateRecovery() { + if (!_shouldRecover()) + return false; + if (claimedScreenName === screenName && _hasOwner(screenName, claimId) && _hasState(screenName, claimId)) + return false; + recoveryRequested(); + return true; + } + + function checkRecovery() { + return checkStateRecovery(); + } + + function beginClaim() { + if (claimId && releaseDockRetract) + releaseDockRetract(claimId); + claimId = _nextClaimId(); + claimedScreenName = ""; + return claimId; + } + + function _syncDockRetract() { + if (!claimId) + return; + if (dockBlocked && presented && dockSide && requestDockRetract) + requestDockRetract(claimId, screenName, dockSide); + else if (releaseDockRetract) + releaseDockRetract(claimId); + } + + function publish(state, forceClaim) { + if (!enabled || !screenName || !state) { + release(); + return false; + } + + if (claimedScreenName && claimedScreenName !== screenName) + release(); + + const current = _isCurrent(screenName); + let claiming = !!forceClaim || !claimId; + if (claiming && !current) + return false; + if (!claimId) + beginClaim(); + + let published = claiming ? claimState(screenName, state, claimId) : ensureState(screenName, state, claimId); + if (!published && !claiming && current) { + if (renewTokenOnRecovery) { + beginClaim(); + } else if (releaseDockRetract) { + releaseDockRetract(claimId); + } + published = claimState(screenName, state, claimId); + } + if (!published) + return false; + + claimedScreenName = screenName; + _syncDockRetract(); + return true; + } + + function updateAnim(animX, animY) { + if (!enabled || !claimId || !claimedScreenName || !updateAnimationState) + return false; + if (!_hasOwner(claimedScreenName, claimId)) { + requestRecovery(); + return false; + } + return updateAnimationState(claimedScreenName, claimId, animX, animY); + } + + function updateBody(bodyX, bodyY, bodyW, bodyH) { + if (!enabled || !claimId || !claimedScreenName || !updateBodyState) + return false; + if (!_hasOwner(claimedScreenName, claimId)) { + requestRecovery(); + return false; + } + return updateBodyState(claimedScreenName, claimId, bodyX, bodyY, bodyW, bodyH); + } + + function release() { + if (!claimId) { + claimedScreenName = ""; + return false; + } + + const releasedClaimId = claimId; + const releasedScreenName = claimedScreenName; + claimId = ""; + claimedScreenName = ""; + + if (releaseDockRetract) + releaseDockRetract(releasedClaimId); + if (releasedScreenName) + return !!releaseState(releasedScreenName, releasedClaimId); + return false; + } + + Component.onDestruction: release() +} diff --git a/quickshell/Common/ElevationShadow.qml b/quickshell/Common/ElevationShadow.qml index 1ccf6b1c..45ba3647 100644 --- a/quickshell/Common/ElevationShadow.qml +++ b/quickshell/Common/ElevationShadow.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects import qs.Common Item { @@ -19,7 +18,11 @@ Item { property real bottomRightRadius: targetRadius property color borderColor: "transparent" property real borderWidth: 0 - property bool useCustomSource: false + + property real sourceX: 0 + property real sourceY: 0 + property real sourceWidth: width + property real sourceHeight: height property bool shadowEnabled: Theme.elevationEnabled property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0 @@ -28,36 +31,24 @@ Item { property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset) property color shadowColor: Theme.elevationShadowColor(level) property real shadowOpacity: 1 - property real blurMax: Theme.elevationBlurMax - property alias sourceRect: sourceRect + readonly property var _ambient: Theme.elevationAmbient(level) + readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0 - layer.enabled: shadowEnabled - - layer.effect: MultiEffect { - autoPaddingEnabled: true - shadowEnabled: true - blurEnabled: false - maskEnabled: false - shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax))) - shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height)) - shadowHorizontalOffset: root.shadowOffsetX - shadowVerticalOffset: root.shadowOffsetY - blurMax: root.blurMax - shadowColor: root.shadowColor - shadowOpacity: root.shadowOpacity - } - - Rectangle { - id: sourceRect + ShaderEffect { anchors.fill: parent - visible: !root.useCustomSource - topLeftRadius: root.topLeftRadius - topRightRadius: root.topRightRadius - bottomLeftRadius: root.bottomLeftRadius - bottomRightRadius: root.bottomRightRadius - color: root.targetColor - border.color: root.borderColor - border.width: root.borderWidth + anchors.margins: -root._pad + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/elevation_rect.frag.qsb") + + property real widthPx: width + property real heightPx: height + property real borderWidth: root.borderWidth + property vector4d rectPx: Qt.vector4d(root._pad + root.sourceX, root._pad + root.sourceY, root.sourceWidth, root.sourceHeight) + property vector4d cornerRadius: Qt.vector4d(root.topLeftRadius, root.topRightRadius, root.bottomRightRadius, root.bottomLeftRadius) + property vector4d fillColor: Qt.vector4d(root.targetColor.r, root.targetColor.g, root.targetColor.b, root.targetColor.a) + property vector4d borderColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a) + property vector4d shadowColor: Qt.vector4d(root.shadowColor.r, root.shadowColor.g, root.shadowColor.b, root.shadowEnabled ? root.shadowColor.a * root.shadowOpacity : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, root.shadowBlurPx), root.shadowSpreadPx, root.shadowOffsetX, root.shadowOffsetY) + property vector4d ambientParam: Qt.vector4d(root._ambient.blurPx, root._ambient.spreadPx, root.shadowEnabled ? root._ambient.alpha * root.shadowOpacity : 0, 0) } } diff --git a/quickshell/Common/KeyboardFocus.qml b/quickshell/Common/KeyboardFocus.qml new file mode 100644 index 00000000..f7a52141 --- /dev/null +++ b/quickshell/Common/KeyboardFocus.qml @@ -0,0 +1,44 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Services + +// Manages keyboard focus policy for popouts, modals, and Hyprland focus grabs +Singleton { + id: root + + function keyboardFocus(active, customFocus) { + if (PopoutManager.screenshotActive) + return WlrKeyboardFocus.None; + if (customFocus !== null && customFocus !== undefined) + return customFocus; + if (!active) + return WlrKeyboardFocus.None; + if (CompositorService.useHyprlandFocusGrab) + return WlrKeyboardFocus.OnDemand; + return WlrKeyboardFocus.Exclusive; + } + + function wantsGrab(active, customFocus) { + return CompositorService.useHyprlandFocusGrab && keyboardFocus(active, customFocus) === WlrKeyboardFocus.OnDemand; + } + + property list barWindows: [] + + function registerBarWindow(window) { + if (!window || barWindows.indexOf(window) !== -1) + return; + barWindows = barWindows.concat([window]); + } + + function unregisterBarWindow(window) { + const idx = barWindows.indexOf(window); + if (idx === -1) + return; + const next = barWindows.slice(); + next.splice(idx, 1); + barWindows = next; + } +} diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index 658c8afe..fd03fed8 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -911,6 +911,16 @@ Singleton { } return Qt.rgba(r, g, b, alpha); } + function elevationAmbient(level) { + const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0; + const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5; + return { + blurPx: blur * 1.75, + spreadPx: 1, + alpha: alpha + }; + } + function elevationTintOpacity(level) { if (!level) return 0; diff --git a/quickshell/Modals/BluetoothPairingModal.qml b/quickshell/Modals/BluetoothPairingModal.qml index 21b0b411..054d1f9a 100644 --- a/quickshell/Modals/BluetoothPairingModal.qml +++ b/quickshell/Modals/BluetoothPairingModal.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -11,11 +10,6 @@ DankModal { layerNamespace: "dms:bluetooth-pairing" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property string deviceName: "" property string deviceAddress: "" property string requestType: "" diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index d40c3134..ef469f63 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import Quickshell.Hyprland import qs.Common import qs.Modals.Clipboard import qs.Modals.Common @@ -12,11 +11,6 @@ DankModal { layerNamespace: "dms:clipboard" - HyprlandFocusGrab { - windows: [clipboardHistoryModal.contentWindow] - active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus - } - function toggle() { if (shouldBeVisible) { hide(); @@ -64,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 11e74a6d..820bb249 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell.Hyprland import qs.Common import qs.Services @@ -52,8 +53,13 @@ Item { focus: true anchors.fill: parent } + + // Hyprland OnDemand grab delivers keyboard focus to the modal content surface. + HyprlandFocusGrab { + 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 @@ -96,8 +102,6 @@ Item { } } - // Defer Loader source-component swap until impl is fully closed; avoids - // tearing down a modal mid-animation when frame mode is toggled. function _maybeResolveBackend() { if (_resolvedBackend === _desiredBackend) return; diff --git a/quickshell/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml index 30618bb1..37e82dc0 100644 --- a/quickshell/Modals/Common/DankModalConnected.qml +++ b/quickshell/Modals/Common/DankModalConnected.qml @@ -31,7 +31,6 @@ Item { property bool closeOnBackgroundClick: true property string animationType: "scale" - // Opposite side from the launcher by default; subclasses may override property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences) @@ -87,16 +86,13 @@ 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 signal backgroundClicked - // Coalesce per-channel dirty bits; one ConnectedModeState write per tick. Timer { id: _syncTimer interval: 0 @@ -115,6 +111,7 @@ Item { id: modalChrome modalHandle: root.modalHandle claimPrefix: root.layerNamespace + ":modal" + surfaceKind: "modal" screenName: root._currentScreenName() enabled: root.frameOwnsConnectedChrome active: root.shouldBeVisible @@ -125,17 +122,38 @@ Item { } function _publishModalChromeState() { + const presented = shouldBeVisible || contentWindow.visible; + const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open")); + const bodyRect = { + "x": alignedX, + "y": alignedY, + "width": alignedWidth, + "height": alignedHeight + }; + const animationOffset = { + "x": modalContainer ? modalContainer.animX : 0, + "y": modalContainer ? modalContainer.animY : 0 + }; const state = { - "visible": shouldBeVisible || contentWindow.visible, + "kind": "modal", + "screenName": root._currentScreenName(), + "phase": phase, + "visible": presented, + "presented": presented, "barSide": resolvedConnectedBarSide, + "bodyRect": bodyRect, + "animationOffset": animationOffset, + "scale": 1, + "opacity": Theme.connectedSurfaceColor.a, "bodyX": alignedX, "bodyY": alignedY, "bodyW": alignedWidth, "bodyH": alignedHeight, - "animX": modalContainer ? modalContainer.animX : 0, - "animY": modalContainer ? modalContainer.animY : 0, + "animX": animationOffset.x, + "animY": animationOffset.y, "omitStartConnector": false, - "omitEndConnector": false + "omitEndConnector": false, + "dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : "" }; return modalChrome.publish(state); } @@ -222,22 +240,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(); @@ -264,8 +276,6 @@ Item { ModalManager.closeModal(modalHandle); closeTimer.stop(); contentWindow.visible = false; - if (!useSingleWindow) - clickCatcher.visible = false; dialogClosed(); Qt.callLater(() => animationsEnabled = true); } @@ -304,8 +314,6 @@ Item { const newScreen = CompositorService.getFocusedScreen(); if (newScreen) { contentWindow.screen = newScreen; - if (!useSingleWindow) - clickCatcher.screen = newScreen; } } } @@ -317,29 +325,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) @@ -349,7 +340,6 @@ Item { return SettingsData.frameEdgeInsetForSide(effectiveScreen, side); } - // frameEdgeInsetForSide is the full inset; do not add frameBarSize readonly property real _connectedAlignedX: { switch (resolvedConnectedBarSide) { case "top": @@ -412,57 +402,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 @@ -472,8 +411,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 @@ -487,36 +426,15 @@ Item { "error": true }) WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldHaveFocus) - return WlrKeyboardFocus.None; - if (root.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus) 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; @@ -528,7 +446,7 @@ Item { MouseArea { anchors.fill: parent - enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible + enabled: root.closeOnBackgroundClick && root.shouldBeVisible z: -2 onClicked: root.backgroundClicked() } @@ -537,7 +455,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 { @@ -551,249 +469,256 @@ Item { } 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 + // Clip to final footprint while frame-owned chrome grows from the bar edge. + 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 + 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: + 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 + + 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 3bf96a75..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 @@ -259,15 +260,7 @@ Item { "error": true }) WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldHaveFocus) - return WlrKeyboardFocus.None; - if (root.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus) anchors { left: true diff --git a/quickshell/Modals/DankColorPickerModal.qml b/quickshell/Modals/DankColorPickerModal.qml index f868db62..f7cbbb12 100644 --- a/quickshell/Modals/DankColorPickerModal.qml +++ b/quickshell/Modals/DankColorPickerModal.qml @@ -1,6 +1,5 @@ import QtQuick import Quickshell -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common @@ -13,11 +12,6 @@ DankModal { layerNamespace: "dms:color-picker" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property string pickerTitle: I18n.tr("Choose Color") property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary property var onColorSelectedCallback: null diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index fef28659..15a3b2bf 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -30,7 +30,6 @@ Item { property string _pendingMode: "" readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose - // Animation state — matches DankPopout/DankModal pattern property bool animationsEnabled: true property bool _motionActive: false property real _frozenMotionX: 0 @@ -108,8 +107,6 @@ Item { return SettingsData.frameEdgeInsetForSide(effectiveScreen, side); } - // frameEdgeInsetForSide is the full inset; do not add frameBarSize. - // Positions the modal flush to the emerge side, centered on the cross axis. readonly property var _connectedModalPos: { const fallback = { "x": (screenWidth - modalWidth) / 2, @@ -175,8 +172,6 @@ Item { readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled - // Shadow padding for the content window (render padding only, no motion padding). - // Zeroed when frame owns the chrome and Wayland clips past the bar edge readonly property var shadowLevel: Theme.elevationLevel3 readonly property real shadowFallbackOffset: 6 readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0 @@ -203,29 +198,11 @@ 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) + readonly property real _ccX: _connectedChromeX + readonly property real _ccY: _connectedChromeY signal dialogClosed - // Coalesce per-channel dirty bits; one ConnectedModeState write per tick. Timer { id: _syncTimer interval: 0 @@ -242,6 +219,7 @@ Item { id: modalChrome modalHandle: root.modalHandle claimPrefix: "dms:launcher-v2" + surfaceKind: "launcher" screenName: root._currentScreenName() enabled: root.frameOwnsConnectedChrome active: root.spotlightOpen @@ -252,17 +230,38 @@ Item { } function _publishModalChromeState() { + const presented = spotlightOpen || contentWindow.visible; + const phase = !presented ? "hidden" : (isClosing ? "closing" : (!contentWindow.visible ? "opening" : "open")); + const bodyRect = { + "x": _connectedChromeX, + "y": _connectedChromeY, + "width": _connectedChromeWidth, + "height": _connectedChromeHeight + }; + const animationOffset = { + "x": contentContainer ? contentContainer.animX : 0, + "y": contentContainer ? contentContainer.animY : 0 + }; const state = { - "visible": spotlightOpen || contentWindow.visible, + "kind": "launcher", + "screenName": root._currentScreenName(), + "phase": phase, + "visible": presented, + "presented": presented, "barSide": resolvedConnectedBarSide, + "bodyRect": bodyRect, + "animationOffset": animationOffset, + "scale": 1, + "opacity": Theme.connectedSurfaceColor.a, "bodyX": _connectedChromeX, "bodyY": _connectedChromeY, "bodyW": _connectedChromeWidth, "bodyH": _connectedChromeHeight, - "animX": contentContainer ? contentContainer.animX : 0, - "animY": contentContainer ? contentContainer.animY : 0, + "animX": animationOffset.x, + "animY": animationOffset.y, "omitStartConnector": false, - "omitEndConnector": false + "omitEndConnector": false, + "dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : "" }; return modalChrome.publish(state); } @@ -359,8 +358,6 @@ Item { return; contentVisible = true; spotlightContent.closeTransientUi?.(); - // NOTE: forceActiveFocus() is deliberately NOT called here. - // It is deferred to after animation starts to avoid compositor IPC stalls. if (spotlightContent.searchField) { spotlightContent.searchField.text = query; @@ -398,40 +395,29 @@ Item { isClosing = false; openedFromOverview = false; - // Disable animations so the snap is instant animationsEnabled = false; - // Freeze the collapsed offsets (they depend on height which could change) _frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0; _frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset); var focusedScreen = CompositorService.getFocusedScreen(); if (focusedScreen) { - backgroundWindow.screen = focusedScreen; contentWindow.screen = focusedScreen; } - // _motionActive = false ensures motionX/Y snap to frozen collapsed position _motionActive = false; - // Make windows visible but do NOT request keyboard focus yet ModalManager.openModal(modalHandle); spotlightOpen = true; - backgroundWindow.visible = true; contentWindow.visible = true; - if (useHyprlandFocusGrab) - focusGrab.active = true; - // Load content and initialize (but no forceActiveFocus — that's deferred) _ensureContentLoadedAndInitialize(query || "", mode || ""); - // Frame 1: enable animations and trigger enter motion + // Defer focus until after enter motion starts (avoids compositor IPC stalls). Qt.callLater(() => { root.animationsEnabled = true; root._motionActive = true; - // Frame 2: request keyboard focus + activate search field - // Double-deferred to avoid compositor IPC competing with animation frames Qt.callLater(() => { root.keyboardActive = true; if (root.spotlightContent && root.spotlightContent.searchField) @@ -454,16 +440,13 @@ Item { spotlightContent?.closeTransientUi?.(); openedFromOverview = false; isClosing = true; - // For directional effects, defer contentVisible=false so content stays rendered during exit slide if (!Theme.isDirectionalEffect) contentVisible = false; - // Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position _motionActive = false; keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); } @@ -500,7 +483,6 @@ Item { isClosing = false; contentVisible = false; contentWindow.visible = false; - backgroundWindow.visible = false; if (root.unloadContentOnClose) launcherContentLoader.active = false; dialogClosed(); @@ -519,7 +501,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [contentWindow] - active: false + active: root.useHyprlandFocusGrab && root.spotlightOpen onCleared: { if (spotlightOpen) { @@ -569,7 +551,6 @@ Item { root._releaseModalChrome(); root._windowEnabled = false; - backgroundWindow.screen = newScreen; contentWindow.screen = newScreen; Qt.callLater(() => { root._windowEnabled = true; @@ -577,73 +558,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 @@ -663,23 +577,31 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) 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 + } + } + + 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 { @@ -691,16 +613,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 @@ -755,7 +692,6 @@ Item { return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40); } - // openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1). QtObject { id: morph property real openProgress: root._motionActive ? 1 : 0 @@ -814,7 +750,6 @@ Item { width: contentContainer.width height: contentContainer.height - // Shadow mirrors contentWrapper position/scale/opacity ElevationShadow { id: launcherShadowLayer width: parent.width @@ -832,7 +767,6 @@ Item { shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" } - // contentWrapper moves inside static contentContainer — DankPopout pattern Item { id: contentWrapper width: parent.width diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml index 44e5e83b..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 @@ -164,8 +165,6 @@ Item { openedFromOverview = false; keyboardActive = true; ModalManager.openModal(modalHandle); - if (useHyprlandFocusGrab) - focusGrab.active = true; _ensureContentLoadedAndInitialize(query || "", mode || ""); } @@ -201,7 +200,6 @@ Item { contentVisible = false; keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); } @@ -231,7 +229,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [launcherWindow] - active: false + active: root.useHyprlandFocusGrab && root.keyboardActive onCleared: { if (spotlightOpen) hide(); @@ -270,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 @@ -337,24 +336,24 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) 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 @@ -364,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() } @@ -396,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 69f1944e..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"], @@ -172,8 +173,6 @@ Item { keyboardActive = true; ModalManager.openModal(modalHandle); - if (useHyprlandFocusGrab) - focusGrab.active = true; _ensureContentLoadedAndInitialize(query || "", mode || ""); } @@ -211,7 +210,6 @@ Item { keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); @@ -262,7 +260,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [launcherWindow] - active: false + active: root.useHyprlandFocusGrab && root.keyboardActive onCleared: { if (spotlightOpen) { @@ -306,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 @@ -373,24 +372,24 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) 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 @@ -400,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() } @@ -432,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/Modals/KeybindsModal.qml b/quickshell/Modals/KeybindsModal.qml index 64b01773..78d2f330 100644 --- a/quickshell/Modals/KeybindsModal.qml +++ b/quickshell/Modals/KeybindsModal.qml @@ -1,7 +1,6 @@ import QtQml import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -29,11 +28,6 @@ DankModal { KeybindsService.loadCheatsheet(); } - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - function scrollDown() { if (!root.activeFlickable) return; diff --git a/quickshell/Modals/MuxModal.qml b/quickshell/Modals/MuxModal.qml index 56ffe9e7..b216bc0c 100644 --- a/quickshell/Modals/MuxModal.qml +++ b/quickshell/Modals/MuxModal.qml @@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell.Hyprland import Quickshell.Io import Quickshell import qs.Common @@ -45,12 +44,6 @@ DankModal { } } - HyprlandFocusGrab { - id: grab - windows: [muxModal.contentWindow] - active: CompositorService.isHyprland && muxModal.shouldHaveFocus - } - function toggle() { if (shouldBeVisible) { hide(); diff --git a/quickshell/Modals/NotificationModal.qml b/quickshell/Modals/NotificationModal.qml index f0e5e1a2..40523b77 100644 --- a/quickshell/Modals/NotificationModal.qml +++ b/quickshell/Modals/NotificationModal.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common @@ -11,11 +10,6 @@ DankModal { layerNamespace: "dms:notification-center-modal" - HyprlandFocusGrab { - windows: [notificationModal.contentWindow] - active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus - } - property bool notificationModalOpen: false property var notificationListRef: null property var historyListRef: null diff --git a/quickshell/Modals/PowerMenuModal.qml b/quickshell/Modals/PowerMenuModal.qml index 8a6473a5..e4836f7d 100644 --- a/quickshell/Modals/PowerMenuModal.qml +++ b/quickshell/Modals/PowerMenuModal.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Effects import Quickshell -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -13,11 +12,6 @@ DankModal { layerNamespace: "dms:power-menu" keepPopoutsOpen: true - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property int selectedIndex: 0 property int selectedRow: 0 property int selectedCol: 0 diff --git a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml index 6c80e3dc..29bae752 100644 --- a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml +++ b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml @@ -109,15 +109,7 @@ DankPopout { close(); } - customKeyboardFocus: { - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (anyModalOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null onBackgroundClicked: close() diff --git a/quickshell/Modules/DankBar/BarCanvas.qml b/quickshell/Modules/DankBar/BarCanvas.qml index 26a2be2b..f82db6c0 100644 --- a/quickshell/Modules/DankBar/BarCanvas.qml +++ b/quickshell/Modules/DankBar/BarCanvas.qml @@ -61,7 +61,7 @@ Item { // M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0 readonly property var elevLevel: Theme.elevationLevel2 - readonly property bool shadowEnabled: !BlurService.enabled && ((Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride) + readonly property bool shadowEnabled: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top"))) readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit" @@ -207,7 +207,6 @@ Item { shadowOffsetX: root.shadowOffsetX shadowOffsetY: root.shadowOffsetY shadowColor: root.shadowColor - blurMax: Theme.elevationBlurMax } Loader { diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 8cca42dc..351762d6 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -9,6 +9,8 @@ PanelWindow { id: barWindow readonly property var log: Log.scoped("DankBarWindow") + Component.onDestruction: KeyboardFocus.unregisterBarWindow(barWindow) + required property var rootWindow required property var barConfig property var modelData: item @@ -18,6 +20,8 @@ PanelWindow { property var centerWidgetsModel property var rightWidgetsModel + readonly property bool barRevealed: inputMask.showing + property var controlCenterButtonRef: null property var clockButtonRef: null property var systemUpdateButtonRef: null @@ -555,6 +559,7 @@ PanelWindow { color: "transparent" Component.onCompleted: { + KeyboardFocus.registerBarWindow(barWindow); updateGpuTempConfig(); _updateBackgroundAlpha(); _updateHasMaximizedToplevel(); @@ -956,8 +961,13 @@ PanelWindow { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: { const screenName = barWindow.screen?.name; - if (screenName && PopoutManager.currentPopoutsByScreen[screenName]) + if (!screenName) + return; + if (PopoutManager.currentPopoutsByScreen[screenName]) PopoutManager.closeAllPopouts(); + if (ModalManager.currentModalsByScreen[screenName]) + ModalManager.closeAllModalsExcept(null); + TrayMenuManager.closeAllMenus(); } } diff --git a/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml b/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml index faf67fc0..7f7011f2 100644 --- a/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml +++ b/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml @@ -38,15 +38,7 @@ DankPopout { backgroundInteractive: !anyModalOpen - customKeyboardFocus: { - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (anyModalOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null Connections { target: SystemUpdateService diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 78853995..6fd746fb 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -980,21 +980,13 @@ BasePill { screen: root.parentScreen WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (!root.menuOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.menuOpen, null) WlrLayershell.namespace: "dms:tray-overflow-menu" color: "transparent" HyprlandFocusGrab { - windows: [overflowMenu] - active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen + windows: [overflowMenu].concat(KeyboardFocus.barWindows) + active: root.useOverflowPopup && KeyboardFocus.wantsGrab(root.menuOpen, null) } Connections { @@ -1051,32 +1043,21 @@ BasePill { "leftBar": 0, "rightBar": 0 }) - readonly property real effectiveBarSize: root.barThickness + root.barSpacing + readonly property real maskX: _overflowDismissZone.x + readonly property real maskY: _overflowDismissZone.y + readonly property real maskWidth: _overflowDismissZone.width + readonly property real maskHeight: _overflowDismissZone.height - readonly property real maskX: { - const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarX, adjacentLeftBar); - } - - readonly property real maskY: { - const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarY, adjacentTopBar); - } - - readonly property real maskWidth: { - const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0; - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar); - return Math.max(100, width - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0; - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar); - return Math.max(100, height - maskY - bottomExclusion); + DismissZone { + id: _overflowDismissZone + barPosition: overflowMenu.barPosition + barX: overflowMenu.barX + barY: overflowMenu.barY + barWidth: overflowMenu.barWidth + barHeight: overflowMenu.barHeight + screenWidth: overflowMenu.width + screenHeight: overflowMenu.height + adjacentBarInfo: overflowMenu.adjacentBarInfo } mask: Region { @@ -1237,13 +1218,7 @@ BasePill { fallbackOffset: 6 targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetRadius: Theme.cornerRadius - sourceRect.antialiasing: true - sourceRect.smooth: true - shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled - layer.smooth: true - layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically - layer.samples: 4 + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled } Rectangle { @@ -1450,20 +1425,12 @@ BasePill { screen: menuRoot.parentScreen WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (!menuRoot.showMenu) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(menuRoot.showMenu, null) color: "transparent" HyprlandFocusGrab { - windows: [menuWindow] - active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu + windows: [menuWindow].concat(KeyboardFocus.barWindows) + active: KeyboardFocus.wantsGrab(menuRoot.showMenu, null) } anchors { @@ -1502,32 +1469,21 @@ BasePill { "leftBar": 0, "rightBar": 0 }) - readonly property real effectiveBarSize: root.barThickness + root.barSpacing + readonly property real maskX: _menuDismissZone.x + readonly property real maskY: _menuDismissZone.y + readonly property real maskWidth: _menuDismissZone.width + readonly property real maskHeight: _menuDismissZone.height - readonly property real maskX: { - const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarX, adjacentLeftBar); - } - - readonly property real maskY: { - const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarY, adjacentTopBar); - } - - readonly property real maskWidth: { - const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0; - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar); - return Math.max(100, width - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0; - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar); - return Math.max(100, height - maskY - bottomExclusion); + DismissZone { + id: _menuDismissZone + barPosition: menuWindow.barPosition + barX: menuWindow.barX + barY: menuWindow.barY + barWidth: menuWindow.barWidth + barHeight: menuWindow.barHeight + screenWidth: menuWindow.width + screenHeight: menuWindow.height + adjacentBarInfo: menuWindow.adjacentBarInfo } mask: Region { @@ -1689,11 +1645,7 @@ BasePill { fallbackOffset: 6 targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetRadius: Theme.cornerRadius - sourceRect.antialiasing: true - shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled - layer.smooth: true - layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled } Rectangle { diff --git a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml index 8a4466e8..797ba8b0 100644 --- a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml +++ b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml @@ -130,7 +130,7 @@ Item { borderColor: volumePanel.border.color borderWidth: volumePanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { @@ -272,7 +272,7 @@ Item { borderColor: audioDevicesPanel.border.color borderWidth: audioDevicesPanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { @@ -444,7 +444,7 @@ Item { borderColor: playersPanel.border.color borderWidth: playersPanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index 413d4dfb..e302f598 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -186,13 +186,36 @@ Variants { return; } + const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps; + const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open")); + const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x; + const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y; + const bodyW = dock.hasApps ? dockBackground.width : 0; + const bodyH = dock.hasApps ? dockBackground.height : 0; ConnectedModeState.setDockState(dock._dockScreenName, { - "reveal": dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps, + "kind": "dock", + "screenName": dock._dockScreenName, + "phase": phase, + "visible": presented, + "presented": presented, + "reveal": presented, "barSide": dock.connectedBarSide, - "bodyX": dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x, - "bodyY": dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y, - "bodyW": dock.hasApps ? dockBackground.width : 0, - "bodyH": dock.hasApps ? dockBackground.height : 0, + "bodyRect": { + "x": bodyX, + "y": bodyY, + "width": bodyW, + "height": bodyH + }, + "animationOffset": { + "x": dockSlide.x, + "y": dockSlide.y + }, + "scale": 1, + "opacity": Theme.connectedSurfaceColor.a, + "bodyX": bodyX, + "bodyY": bodyY, + "bodyW": bodyW, + "bodyH": bodyH, "slideX": dockSlide.x, "slideY": dockSlide.y }); @@ -724,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/FrameBorder.qml b/quickshell/Modules/Frame/FrameBorder.qml index 31112382..976817ad 100644 --- a/quickshell/Modules/Frame/FrameBorder.qml +++ b/quickshell/Modules/Frame/FrameBorder.qml @@ -1,9 +1,9 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects import qs.Common +// Frame perimeter ring with rounded cutout (SDF). Item { id: root @@ -16,39 +16,14 @@ Item { required property real cutoutRadius property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) - Rectangle { - id: borderRect - + ShaderEffect { anchors.fill: parent - // Bake frameOpacity into the color alpha rather than using the `opacity` property - color: root.borderColor + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/frame_arc.frag.qsb") - layer.enabled: true - layer.effect: MultiEffect { - maskSource: cutoutMask - maskEnabled: true - maskInverted: true - maskThresholdMin: 0.5 - maskSpreadAtMin: 1 - } - } - - Item { - id: cutoutMask - - anchors.fill: parent - layer.enabled: true - visible: false - - Rectangle { - anchors { - fill: parent - topMargin: root.cutoutTopInset - bottomMargin: root.cutoutBottomInset - leftMargin: root.cutoutLeftInset - rightMargin: root.cutoutRightInset - } - radius: root.cutoutRadius - } + property real widthPx: width + property real heightPx: height + property real cutoutRadius: root.cutoutRadius + property vector4d cutout: Qt.vector4d(root.cutoutLeftInset, root.cutoutTopInset, root.width - root.cutoutRightInset, root.height - root.cutoutBottomInset) + property vector4d surfaceColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a) } } diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index c67c8d43..880a266c 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -1,13 +1,12 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Common import qs.Services -import qs.Widgets import "../../Common/ConnectorGeometry.js" as ConnectorGeometry +import "../../Common/ConnectedSurfaceGeometry.js" as SurfaceGeometry PanelWindow { id: win @@ -46,13 +45,10 @@ PanelWindow { readonly property int _windowRegionHeight: win._regionInt(win.height) readonly property string _screenName: win.targetScreen ? win.targetScreen.name : "" readonly property int _surfaceRevision: Number(ConnectedModeState.surfaceRevisions[win._screenName] || 0) - readonly property var _dockState: ConnectedModeState.dockStates[win._screenName] || ConnectedModeState.emptyDockState - readonly property var _dockSlide: ConnectedModeState.dockSlides[win._screenName] || ({ - "x": 0, - "y": 0 - }) - readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState - readonly property var _modalState: ConnectedModeState.modalStates[win._screenName] || ConnectedModeState.emptyModalState + readonly property var _popoutDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "popout") + 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 bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen) readonly property string _barSide: { @@ -67,10 +63,12 @@ PanelWindow { } readonly property real _ccr: Theme.connectedCornerRadius - readonly property bool _popoutHorizontal: ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom" - readonly property bool _notifHorizontal: ConnectorGeometry.isHorizontal(win._notifState.barSide) - readonly property bool _modalHorizontal: ConnectorGeometry.isHorizontal(win._modalState.barSide) - readonly property bool _dockHorizontal: ConnectorGeometry.isHorizontal(win._dockState.barSide) + readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.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) + readonly property var _dockBodyGeometry: SurfaceGeometry.translatedBodyRect(win._dockDescriptor, win._dpr) readonly property real _popoutArcExtent: win._popoutHorizontal ? _popoutBodyBlurAnchor.height : _popoutBodyBlurAnchor.width readonly property real _modalArcExtent: win._modalHorizontal ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width @@ -84,76 +82,111 @@ 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 _popoutFillOverlapXValue: win._popoutHorizontal ? win._seamOverlap : 0 - readonly property real _popoutFillOverlapYValue: (ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right") ? win._seamOverlap : 0 - readonly property real _dockFillOverlapXValue: win._dockHorizontal ? win._seamOverlap : 0 - readonly property real _dockFillOverlapYValue: (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0 - readonly property real _dockJoinOverlapXValue: ConnectorGeometry.isVertical(win._dockState.barSide) ? win._seamOverlap : 0 - readonly property real _dockJoinOverlapYValue: ConnectorGeometry.isHorizontal(win._dockState.barSide) ? win._seamOverlap : 0 - 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. - readonly property real _effectivePopoutCcr: { - const crossSize = win._popoutHorizontal ? _popoutBodyBlurAnchor.width : _popoutBodyBlurAnchor.height; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._popoutArcExtent, crossSize / 2)), win._dpr); - } - readonly property real _effectivePopoutFarCcr: { - const crossSize = win._popoutHorizontal ? _popoutBodyBlurAnchor.width : _popoutBodyBlurAnchor.height; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr); - } - readonly property real _effectivePopoutStartCcr: ConnectedModeState.popoutOmitStartConnector ? 0 : win._effectivePopoutCcr - readonly property real _effectivePopoutEndCcr: ConnectedModeState.popoutOmitEndConnector ? 0 : win._effectivePopoutCcr - readonly property real _effectivePopoutFarStartCcr: ConnectedModeState.popoutOmitStartConnector ? win._effectivePopoutFarCcr : 0 - readonly property real _effectivePopoutFarEndCcr: ConnectedModeState.popoutOmitEndConnector ? win._effectivePopoutFarCcr : 0 + readonly property var _popoutRadii: SurfaceGeometry.connectorRadii(win._popoutDescriptor, win._popoutBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, false) + readonly property real _effectivePopoutCcr: win._popoutRadii.near + readonly property real _effectivePopoutFarCcr: win._popoutRadii.far + readonly property real _effectivePopoutStartCcr: win._popoutRadii.start + readonly property real _effectivePopoutEndCcr: win._popoutRadii.end + readonly property real _effectivePopoutFarStartCcr: win._popoutRadii.farStart + readonly property real _effectivePopoutFarEndCcr: win._popoutRadii.farEnd readonly property real _effectivePopoutMaxCcr: Math.max(win._effectivePopoutStartCcr, win._effectivePopoutEndCcr) readonly property real _effectivePopoutFarExtent: Math.max(win._effectivePopoutFarStartCcr, win._effectivePopoutFarEndCcr) - readonly property real _effectiveNotifCcr: { - const crossSize = win._notifHorizontal ? _notifBodyBlurAnchor.width : _notifBodyBlurAnchor.height; - const extent = win._notifHorizontal ? _notifBodyBlurAnchor.height : _notifBodyBlurAnchor.width; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, extent, crossSize / 2)), win._dpr); - } - readonly property real _effectiveNotifFarCcr: { - const crossSize = win._notifHorizontal ? _notifBodySceneBlurAnchor.width : _notifBodySceneBlurAnchor.height; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr); - } - readonly property real _effectiveNotifStartCcr: win._notifState.omitStartConnector ? 0 : win._effectiveNotifCcr - readonly property real _effectiveNotifEndCcr: win._notifState.omitEndConnector ? 0 : win._effectiveNotifCcr - readonly property real _effectiveNotifFarStartCcr: win._notifState.omitStartConnector ? win._effectiveNotifFarCcr : 0 - readonly property real _effectiveNotifFarEndCcr: win._notifState.omitEndConnector ? win._effectiveNotifFarCcr : 0 + readonly property var _notifNearRadii: SurfaceGeometry.connectorRadii(win._notifDescriptor, win._notifBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, true) + readonly property var _notifFarRadii: SurfaceGeometry.connectorRadii(win._notifDescriptor, win._notifBodyScene(), win._ccr, win._surfaceRadius, win._dpr, true) + readonly property real _effectiveNotifCcr: win._notifNearRadii.near + readonly property real _effectiveNotifFarCcr: win._notifFarRadii.far + readonly property real _effectiveNotifStartCcr: win._notifNearRadii.start + readonly property real _effectiveNotifEndCcr: win._notifNearRadii.end + readonly property real _effectiveNotifFarStartCcr: win._notifFarRadii.farStart + readonly property real _effectiveNotifFarEndCcr: win._notifFarRadii.farEnd readonly property real _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr) readonly property real _effectiveNotifFarExtent: Math.max(win._effectiveNotifFarStartCcr, win._effectiveNotifFarEndCcr) - readonly property real _effectiveModalCcr: { - const crossSize = win._modalHorizontal ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height; - const extent = win._modalHorizontal ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, extent, crossSize / 2)), win._dpr); - } - readonly property real _effectiveModalFarCcr: { - const crossSize = win._modalHorizontal ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height; - return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr); - } - readonly property real _effectiveModalStartCcr: win._modalState.omitStartConnector ? 0 : win._effectiveModalCcr - readonly property real _effectiveModalEndCcr: win._modalState.omitEndConnector ? 0 : win._effectiveModalCcr - readonly property real _effectiveModalFarStartCcr: win._modalState.omitStartConnector ? win._effectiveModalFarCcr : 0 - readonly property real _effectiveModalFarEndCcr: win._modalState.omitEndConnector ? win._effectiveModalFarCcr : 0 + readonly property var _modalRadii: SurfaceGeometry.connectorRadii(win._modalDescriptor, win._modalBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, true) + readonly property real _effectiveModalCcr: win._modalRadii.near + readonly property real _effectiveModalFarCcr: win._modalRadii.far + readonly property real _effectiveModalStartCcr: win._modalRadii.start + readonly property real _effectiveModalEndCcr: win._modalRadii.end + readonly property real _effectiveModalFarStartCcr: win._modalRadii.farStart + readonly property real _effectiveModalFarEndCcr: win._modalRadii.farEnd readonly property real _effectiveModalFarExtent: Math.max(win._effectiveModalFarStartCcr, win._effectiveModalFarEndCcr) readonly property color _surfaceColor: Theme.connectedSurfaceColor - readonly property real _surfaceOpacity: _surfaceColor.a - readonly property color _opaqueSurfaceColor: Qt.rgba(_surfaceColor.r, _surfaceColor.g, _surfaceColor.b, 1) readonly property real _surfaceRadius: Theme.connectedSurfaceRadius readonly property real _seamOverlap: Theme.hairline(win._dpr) readonly property bool _disableLayer: Quickshell.env("DMS_DISABLE_LAYER") === "true" || Quickshell.env("DMS_DISABLE_LAYER") === "1" - property bool _surfaceRefreshNeedsLayerRecreate: false - property bool _surfaceLayerRecoveryActive: false - + readonly property bool _elevationShadow: win._connectedActive && Theme.elevationEnabled && !win._disableLayer + // Pack active connected surfaces into four fixed SDF slots (near edges clamp to cutout). + readonly property var _sdfSlots: { + const T = win.cutoutTopInset; + const L = win.cutoutLeftInset; + const R = win.width - win.cutoutRightInset; + const B = win.height - win.cutoutBottomInset; + const clampNear = function (side, b) { + const r = {"x": b.x, "y": b.y, "width": b.width, "height": b.height}; + if (side === "top") { + r.height = Math.max(0, b.y + b.height - T); + r.y = T; + } else if (side === "bottom") { + r.height = Math.max(0, B - b.y); + } else if (side === "left") { + r.width = Math.max(0, b.x + b.width - L); + r.x = L; + } else if (side === "right") { + r.width = Math.max(0, R - b.x); + } + return r; + }; + const src = win._unifiedSurfaces(); + const out = []; + for (let i = 0; i < 4; i++) { + if (i < src.length) { + const s = src[i]; + const b = clampNear(s.side, s.body); + const active = b.width > 0 && b.height > 0 ? 1 : 0; + const sc = s.radii.startCr, ec = s.radii.endCr; + const extent = (s.side === "top" || s.side === "bottom") ? b.height : b.width; + const fc = Math.min(s.radii.farCr, extent); + const omitS = s.radii.farStartCr > 0; + const omitE = s.radii.farEndCr > 0; + const bodyR = s.radii.surfaceRadius; + const nearS = omitS ? bodyR : 0, nearE = omitE ? bodyR : 0; + const farS = omitS ? 0 : bodyR, farE = omitE ? 0 : bodyR; + const kS = omitS ? fc : sc, kE = omitE ? fc : ec; + let ks, cr; + if (s.side === "top") { + ks = [kS, kE, fc, fc]; + cr = [nearS, nearE, farE, farS]; + } else if (s.side === "bottom") { + ks = [fc, fc, kE, kS]; + cr = [farS, farE, nearE, nearS]; + } else if (s.side === "left") { + ks = [kS, fc, fc, kE]; + cr = [nearS, farS, farE, nearE]; + } else { + ks = [fc, kS, kE, fc]; + cr = [farS, nearS, nearE, farE]; + } + out.push({ + "rect": Qt.vector4d(b.x, b.y, b.width, b.height), + "corner": Qt.vector4d(cr[0], cr[1], cr[2], cr[3]), + "k": Qt.vector4d(ks[0], ks[1], ks[2], ks[3]), + "param": Qt.vector4d(active, 0, 0, 0) + }); + } else { + out.push({"rect": Qt.vector4d(0, 0, 0, 0), "corner": Qt.vector4d(0, 0, 0, 0), "k": Qt.vector4d(0, 0, 0, 0), "param": Qt.vector4d(0, 0, 0, 0)}); + } + } + return out; + } function _regionInt(value) { return Math.max(0, Math.round(Theme.px(value, win._dpr))); } @@ -170,6 +203,7 @@ PanelWindow { return Math.max(0, Math.min(requested, maxRadius)); } + readonly property bool _blurSurfacesActive: BlurService.enabled && SettingsData.frameBlurEnabled && win._frameActive readonly property int _blurCutoutCompensation: SettingsData.frameOpacity <= 0.2 ? 1 : 0 readonly property int _blurCutoutLeft: Math.max(0, win.cutoutLeftInset - win._blurCutoutCompensation) readonly property int _blurCutoutTop: Math.max(0, win.cutoutTopInset - win._blurCutoutCompensation) @@ -181,624 +215,14 @@ PanelWindow { return Math.max(0, Math.min(requested, maxRadius)); } - // Invisible items providing scene coordinates for blur Region anchors - Item { - id: _blurCutout - x: win._blurCutoutLeft - y: win._blurCutoutTop - width: Math.max(0, win._blurCutoutRight - win._blurCutoutLeft) - height: Math.max(0, win._blurCutoutBottom - win._blurCutoutTop) - } - - Item { - id: _popoutBodyBlurAnchor - visible: false - - readonly property bool _active: ConnectedModeState.popoutVisible && ConnectedModeState.popoutScreen === win._screenName - - readonly property real _dyClamp: (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? Math.max(-ConnectedModeState.popoutBodyH, Math.min(ConnectedModeState.popoutAnimY, ConnectedModeState.popoutBodyH)) : 0 - readonly property real _dxClamp: (ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right") ? Math.max(-ConnectedModeState.popoutBodyW, Math.min(ConnectedModeState.popoutAnimX, ConnectedModeState.popoutBodyW)) : 0 - - x: _active ? ConnectedModeState.popoutBodyX + (ConnectedModeState.popoutBarSide === "right" ? _dxClamp : 0) : 0 - y: _active ? ConnectedModeState.popoutBodyY + (ConnectedModeState.popoutBarSide === "bottom" ? _dyClamp : 0) : 0 - width: _active ? Math.max(0, ConnectedModeState.popoutBodyW - Math.abs(_dxClamp)) : 0 - height: _active ? Math.max(0, ConnectedModeState.popoutBodyH - Math.abs(_dyClamp)) : 0 - } - - Item { - id: _dockBodyBlurAnchor - visible: false - - readonly property bool _active: win._connectedActive && win._dockState.reveal && win._dockState.bodyW > 0 && win._dockState.bodyH > 0 - - x: _active ? win._dockState.bodyX + (win._dockSlide.x || 0) : 0 - y: _active ? win._dockState.bodyY + (win._dockSlide.y || 0) : 0 - width: _active ? win._dockState.bodyW : 0 - height: _active ? win._dockState.bodyH : 0 - } - - Item { - id: _popoutBodyBlurCap - opacity: 0 - - readonly property string _side: ConnectedModeState.popoutBarSide - readonly property real _capThickness: win._popoutBlurCapThickness() - readonly property bool _active: _popoutBodyBlurAnchor._active && _capThickness > 0 && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0 - readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capThickness, _popoutBodyBlurAnchor.width) : _popoutBodyBlurAnchor.width - readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capThickness, _popoutBodyBlurAnchor.height) : _popoutBodyBlurAnchor.height - - x: !_active ? 0 : (_side === "right" ? _popoutBodyBlurAnchor.x + _popoutBodyBlurAnchor.width - _capWidth : _popoutBodyBlurAnchor.x) - y: !_active ? 0 : (_side === "bottom" ? _popoutBodyBlurAnchor.y + _popoutBodyBlurAnchor.height - _capHeight : _popoutBodyBlurAnchor.y) - width: _active ? _capWidth : 0 - height: _active ? _capHeight : 0 - } - - Item { - id: _dockBodyBlurCap - opacity: 0 - - readonly property string _side: win._dockState.barSide - readonly property bool _active: _dockBodyBlurAnchor._active && _dockBodyBlurAnchor.width > 0 && _dockBodyBlurAnchor.height > 0 - readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.width) : _dockBodyBlurAnchor.width - readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(win._dockConnectorRadiusValue, _dockBodyBlurAnchor.height) : _dockBodyBlurAnchor.height - - x: !_active ? 0 : (_side === "right" ? _dockBodyBlurAnchor.x + _dockBodyBlurAnchor.width - _capWidth : _dockBodyBlurAnchor.x) - y: !_active ? 0 : (_side === "bottom" ? _dockBodyBlurAnchor.y + _dockBodyBlurAnchor.height - _capHeight : _dockBodyBlurAnchor.y) - width: _active ? _capWidth : 0 - height: _active ? _capHeight : 0 - } - - Item { - id: _popoutLeftConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._popoutConnectorRadiusLeft - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(ConnectedModeState.popoutBarSide, 0, win._popoutConnectorRadiusLeft) - readonly property real _h: ConnectorGeometry.connectorHeight(ConnectedModeState.popoutBarSide, 0, win._popoutConnectorRadiusLeft) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(ConnectedModeState.popoutBarSide, _popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, "left", 0, win._popoutConnectorRadiusLeft), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(ConnectedModeState.popoutBarSide, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, "left", 0, win._popoutConnectorRadiusLeft), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _popoutRightConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._popoutConnectorRadiusRight - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(ConnectedModeState.popoutBarSide, 0, win._popoutConnectorRadiusRight) - readonly property real _h: ConnectorGeometry.connectorHeight(ConnectedModeState.popoutBarSide, 0, win._popoutConnectorRadiusRight) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(ConnectedModeState.popoutBarSide, _popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, "right", 0, win._popoutConnectorRadiusRight), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(ConnectedModeState.popoutBarSide, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, "right", 0, win._popoutConnectorRadiusRight), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _popoutLeftConnectorCutout - opacity: 0 - - readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(ConnectedModeState.popoutBarSide, "left") - readonly property real _radius: win._popoutConnectorRadiusLeft - - x: _active ? win._connectorCutoutX(_popoutLeftConnectorBlurAnchor.x, _popoutLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_popoutLeftConnectorBlurAnchor.y, _popoutLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _popoutRightConnectorCutout - opacity: 0 - - readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(ConnectedModeState.popoutBarSide, "right") - readonly property real _radius: win._popoutConnectorRadiusRight - - x: _active ? win._connectorCutoutX(_popoutRightConnectorBlurAnchor.x, _popoutRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_popoutRightConnectorBlurAnchor.y, _popoutRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _popoutFarStartConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectivePopoutFarStartCcr - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _popoutFarStartBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectivePopoutFarStartCcr - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _popoutFarEndBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectivePopoutFarEndCcr - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _popoutFarEndConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectivePopoutFarEndCcr - readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _popoutFarStartConnectorCutout - opacity: 0 - - readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(ConnectedModeState.popoutBarSide, "left") - readonly property string _placement: win._farConnectorPlacement(ConnectedModeState.popoutBarSide, "left") - readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectivePopoutFarStartCcr - - x: _active ? win._connectorCutoutX(_popoutFarStartConnectorBlurAnchor.x, _popoutFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_popoutFarStartConnectorBlurAnchor.y, _popoutFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _popoutFarEndConnectorCutout - opacity: 0 - - readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0 - readonly property string _barSide: win._farConnectorBarSide(ConnectedModeState.popoutBarSide, "right") - readonly property string _placement: win._farConnectorPlacement(ConnectedModeState.popoutBarSide, "right") - readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectivePopoutFarEndCcr - - x: _active ? win._connectorCutoutX(_popoutFarEndConnectorBlurAnchor.x, _popoutFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_popoutFarEndConnectorBlurAnchor.y, _popoutFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _dockLeftConnectorBlurAnchor - opacity: 0 - - readonly property bool _active: _dockBodyBlurAnchor._active && win._dockConnectorRadiusValue > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._dockState.barSide, 0, win._dockConnectorRadiusValue) - readonly property real _h: ConnectorGeometry.connectorHeight(win._dockState.barSide, 0, win._dockConnectorRadiusValue) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._dockState.barSide, _dockBodyBlurAnchor.x, _dockBodyBlurAnchor.width, "left", 0, win._dockConnectorRadiusValue), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._dockState.barSide, _dockBodyBlurAnchor.y, _dockBodyBlurAnchor.height, "left", 0, win._dockConnectorRadiusValue), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _dockRightConnectorBlurAnchor - opacity: 0 - - readonly property bool _active: _dockBodyBlurAnchor._active && win._dockConnectorRadiusValue > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._dockState.barSide, 0, win._dockConnectorRadiusValue) - readonly property real _h: ConnectorGeometry.connectorHeight(win._dockState.barSide, 0, win._dockConnectorRadiusValue) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._dockState.barSide, _dockBodyBlurAnchor.x, _dockBodyBlurAnchor.width, "right", 0, win._dockConnectorRadiusValue), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._dockState.barSide, _dockBodyBlurAnchor.y, _dockBodyBlurAnchor.height, "right", 0, win._dockConnectorRadiusValue), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _dockLeftConnectorCutout - opacity: 0 - - readonly property bool _active: _dockLeftConnectorBlurAnchor.width > 0 && _dockLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "left") - - x: _active ? win._connectorCutoutX(_dockLeftConnectorBlurAnchor.x, _dockLeftConnectorBlurAnchor.width, _arcCorner, win._dockConnectorRadiusValue) : 0 - y: _active ? win._connectorCutoutY(_dockLeftConnectorBlurAnchor.y, _dockLeftConnectorBlurAnchor.height, _arcCorner, win._dockConnectorRadiusValue) : 0 - width: _active ? win._dockConnectorRadiusValue * 2 : 0 - height: _active ? win._dockConnectorRadiusValue * 2 : 0 - } - - Item { - id: _dockRightConnectorCutout - opacity: 0 - - readonly property bool _active: _dockRightConnectorBlurAnchor.width > 0 && _dockRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockState.barSide, "right") - - x: _active ? win._connectorCutoutX(_dockRightConnectorBlurAnchor.x, _dockRightConnectorBlurAnchor.width, _arcCorner, win._dockConnectorRadiusValue) : 0 - y: _active ? win._connectorCutoutY(_dockRightConnectorBlurAnchor.y, _dockRightConnectorBlurAnchor.height, _arcCorner, win._dockConnectorRadiusValue) : 0 - width: _active ? win._dockConnectorRadiusValue * 2 : 0 - height: _active ? win._dockConnectorRadiusValue * 2 : 0 - } - - Item { + QtObject { id: _notifBodyBlurAnchor - visible: false - readonly property bool _active: win._frameActive && win._notifState.visible && win._notifState.bodyW > 0 && win._notifState.bodyH > 0 - - x: _active ? Theme.snap(win._notifState.bodyX, win._dpr) : 0 - y: _active ? Theme.snap(win._notifState.bodyY, win._dpr) : 0 - width: _active ? Theme.snap(win._notifState.bodyW, win._dpr) : 0 - height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0 - } - - Item { - id: _modalBodyBlurAnchor - visible: false - - readonly property bool _active: win._frameActive && win._modalState.visible && win._modalState.bodyW > 0 && win._modalState.bodyH > 0 - - // Clamp animX/Y so the blur body shrinks toward the bar edge (same as _popoutBodyBlurAnchor). - readonly property real _dyClamp: ConnectorGeometry.isHorizontal(win._modalState.barSide) ? Math.max(-win._modalState.bodyH, Math.min(win._modalState.animY, win._modalState.bodyH)) : 0 - readonly property real _dxClamp: (win._modalState.barSide === "left" || win._modalState.barSide === "right") ? Math.max(-win._modalState.bodyW, Math.min(win._modalState.animX, win._modalState.bodyW)) : 0 - - x: _active ? Theme.snap(win._modalState.bodyX + (win._modalState.barSide === "right" ? _dxClamp : 0), win._dpr) : 0 - y: _active ? Theme.snap(win._modalState.bodyY + (win._modalState.barSide === "bottom" ? _dyClamp : 0), win._dpr) : 0 - width: _active ? Theme.snap(Math.max(0, win._modalState.bodyW - Math.abs(_dxClamp)), win._dpr) : 0 - height: _active ? Theme.snap(Math.max(0, win._modalState.bodyH - Math.abs(_dyClamp)), win._dpr) : 0 - } - - Item { - id: _modalBodyBlurCap - opacity: 0 - - readonly property string _side: win._modalState.barSide - readonly property real _capThickness: win._modalBlurCapThickness() - readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0 - readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capThickness, _modalBodyBlurAnchor.width) : _modalBodyBlurAnchor.width - readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capThickness, _modalBodyBlurAnchor.height) : _modalBodyBlurAnchor.height - - x: !_active ? 0 : (_side === "right" ? _modalBodyBlurAnchor.x + _modalBodyBlurAnchor.width - _capWidth : _modalBodyBlurAnchor.x) - y: !_active ? 0 : (_side === "bottom" ? _modalBodyBlurAnchor.y + _modalBodyBlurAnchor.height - _capHeight : _modalBodyBlurAnchor.y) - width: _active ? _capWidth : 0 - height: _active ? _capHeight : 0 - } - - Item { - id: _modalLeftConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._modalConnectorRadiusLeft - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._modalState.barSide, 0, win._modalConnectorRadiusLeft) - readonly property real _h: ConnectorGeometry.connectorHeight(win._modalState.barSide, 0, win._modalConnectorRadiusLeft) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._modalState.barSide, _modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "left", 0, win._modalConnectorRadiusLeft), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._modalState.barSide, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "left", 0, win._modalConnectorRadiusLeft), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _modalRightConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._modalConnectorRadiusRight - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._modalState.barSide, 0, win._modalConnectorRadiusRight) - readonly property real _h: ConnectorGeometry.connectorHeight(win._modalState.barSide, 0, win._modalConnectorRadiusRight) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._modalState.barSide, _modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "right", 0, win._modalConnectorRadiusRight), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._modalState.barSide, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "right", 0, win._modalConnectorRadiusRight), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _modalLeftConnectorCutout - opacity: 0 - - readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "left") - readonly property real _radius: win._modalConnectorRadiusLeft - - x: _active ? win._connectorCutoutX(_modalLeftConnectorBlurAnchor.x, _modalLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_modalLeftConnectorBlurAnchor.y, _modalLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _modalRightConnectorCutout - opacity: 0 - - readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalState.barSide, "right") - readonly property real _radius: win._modalConnectorRadiusRight - - x: _active ? win._connectorCutoutX(_modalRightConnectorBlurAnchor.x, _modalRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_modalRightConnectorBlurAnchor.y, _modalRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _modalFarStartConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectiveModalFarStartCcr - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _modalFarStartBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectiveModalFarStartCcr - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _modalFarEndBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectiveModalFarEndCcr - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _modalFarEndConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectiveModalFarEndCcr - readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _modalFarStartConnectorCutout - opacity: 0 - - 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 _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectiveModalFarStartCcr - - x: _active ? win._connectorCutoutX(_modalFarStartConnectorBlurAnchor.x, _modalFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_modalFarStartConnectorBlurAnchor.y, _modalFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _modalFarEndConnectorCutout - opacity: 0 - - 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 _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectiveModalFarEndCcr - - x: _active ? win._connectorCutoutX(_modalFarEndConnectorBlurAnchor.x, _modalFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_modalFarEndConnectorBlurAnchor.y, _modalFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _notifBodySceneBlurAnchor - visible: false - - readonly property bool _active: _notifBodyBlurAnchor._active - readonly property var _scene: _active ? win._notifBodyScene() : null - - x: _scene ? Theme.snap(_scene.x, win._dpr) : 0 - y: _scene ? Theme.snap(_scene.y, win._dpr) : 0 - width: _scene ? Theme.snap(_scene.width, win._dpr) : 0 - height: _scene ? Theme.snap(_scene.height, win._dpr) : 0 - } - - Item { - id: _notifBodyBlurCap - opacity: 0 - - readonly property string _side: win._notifState.barSide - readonly property real _capRadius: win._effectiveNotifMaxCcr - readonly property bool _active: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 && _capRadius > 0 - readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capRadius, _notifBodySceneBlurAnchor.width) : _notifBodySceneBlurAnchor.width - readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capRadius, _notifBodySceneBlurAnchor.height) : _notifBodySceneBlurAnchor.height - - x: !_active ? 0 : (_side === "right" ? _notifBodySceneBlurAnchor.x + _notifBodySceneBlurAnchor.width - _capWidth : _notifBodySceneBlurAnchor.x) - y: !_active ? 0 : (_side === "bottom" ? _notifBodySceneBlurAnchor.y + _notifBodySceneBlurAnchor.height - _capHeight : _notifBodySceneBlurAnchor.y) - width: _active ? _capWidth : 0 - height: _active ? _capHeight : 0 - } - - Item { - id: _notifLeftConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._notifConnectorRadiusLeft - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._notifState.barSide, 0, win._notifConnectorRadiusLeft) - readonly property real _h: ConnectorGeometry.connectorHeight(win._notifState.barSide, 0, win._notifConnectorRadiusLeft) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._notifState.barSide, _notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, "left", 0, win._notifConnectorRadiusLeft), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._notifState.barSide, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, "left", 0, win._notifConnectorRadiusLeft), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _notifRightConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._notifConnectorRadiusRight - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - readonly property real _w: ConnectorGeometry.connectorWidth(win._notifState.barSide, 0, win._notifConnectorRadiusRight) - readonly property real _h: ConnectorGeometry.connectorHeight(win._notifState.barSide, 0, win._notifConnectorRadiusRight) - - x: _active ? Theme.snap(ConnectorGeometry.connectorX(win._notifState.barSide, _notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, "right", 0, win._notifConnectorRadiusRight), win._dpr) : 0 - y: _active ? Theme.snap(ConnectorGeometry.connectorY(win._notifState.barSide, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, "right", 0, win._notifConnectorRadiusRight), win._dpr) : 0 - width: _active ? _w : 0 - height: _active ? _h : 0 - } - - Item { - id: _notifLeftConnectorCutout - opacity: 0 - - readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "left") - readonly property real _radius: win._notifConnectorRadiusLeft - - x: _active ? win._connectorCutoutX(_notifLeftConnectorBlurAnchor.x, _notifLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_notifLeftConnectorBlurAnchor.y, _notifLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _notifRightConnectorCutout - opacity: 0 - - readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifState.barSide, "right") - readonly property real _radius: win._notifConnectorRadiusRight - - x: _active ? win._connectorCutoutX(_notifRightConnectorBlurAnchor.x, _notifRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_notifRightConnectorBlurAnchor.y, _notifRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _notifFarStartConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectiveNotifFarStartCcr - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _notifFarStartBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectiveNotifFarStartCcr - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, win._notifState.barSide, "left", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _notifFarEndBodyBlurCap - opacity: 0 - - readonly property real _radius: win._effectiveNotifFarEndCcr - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farBodyCapX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, win._notifState.barSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farBodyCapY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _notifFarEndConnectorBlurAnchor - opacity: 0 - - readonly property real _radius: win._effectiveNotifFarEndCcr - readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 - - x: _active ? Theme.snap(win._farConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 - y: _active ? Theme.snap(win._farConnectorY(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 - width: _active ? _radius : 0 - height: _active ? _radius : 0 - } - - Item { - id: _notifFarStartConnectorCutout - opacity: 0 - - 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 _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectiveNotifFarStartCcr - - x: _active ? win._connectorCutoutX(_notifFarStartConnectorBlurAnchor.x, _notifFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_notifFarStartConnectorBlurAnchor.y, _notifFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 - } - - Item { - id: _notifFarEndConnectorCutout - opacity: 0 - - 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 _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) - readonly property real _radius: win._effectiveNotifFarEndCcr - - x: _active ? win._connectorCutoutX(_notifFarEndConnectorBlurAnchor.x, _notifFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0 - y: _active ? win._connectorCutoutY(_notifFarEndConnectorBlurAnchor.y, _notifFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0 - width: _active ? _radius * 2 : 0 - height: _active ? _radius * 2 : 0 + readonly property bool _active: win._blurSurfacesActive && win._notifDescriptor.visible && win._notifBodyGeometry.width > 0 && win._notifBodyGeometry.height > 0 + readonly property int x: _active ? Math.round(win._notifBodyGeometry.x) : 0 + readonly property int y: _active ? Math.round(win._notifBodyGeometry.y) : 0 + readonly property int width: _active ? Math.round(win._notifBodyGeometry.width) : 0 + readonly property int height: _active ? Math.round(win._notifBodyGeometry.height) : 0 } Region { @@ -808,179 +232,581 @@ PanelWindow { width: win._windowRegionWidth height: win._windowRegionHeight - // Frame cutout (always active when frame is on) Region { - item: _blurCutout + id: _blurCutout intersection: Intersection.Subtract radius: win._blurCutoutRadius + x: win._blurCutoutLeft + y: win._blurCutoutTop + width: Math.max(0, win._blurCutoutRight - win._blurCutoutLeft) + height: Math.max(0, win._blurCutoutBottom - win._blurCutoutTop) } Region { - item: _popoutBodyBlurAnchor + id: _popoutBodyBlurAnchor + + readonly property bool _active: win._blurSurfacesActive && win._popoutDescriptor.visible + radius: win._surfaceRadius + x: _active ? Math.round(win._popoutBodyGeometry.x) : 0 + y: _active ? Math.round(win._popoutBodyGeometry.y) : 0 + width: _active ? Math.round(win._popoutBodyGeometry.width) : 0 + height: _active ? Math.round(win._popoutBodyGeometry.height) : 0 } Region { - item: _popoutBodyBlurCap + id: _popoutBodyBlurCap + + 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 + readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(_capThickness, _popoutBodyBlurAnchor.height)) : _popoutBodyBlurAnchor.height + + x: !_active ? 0 : (_side === "right" ? _popoutBodyBlurAnchor.x + _popoutBodyBlurAnchor.width - _capWidth : _popoutBodyBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _popoutBodyBlurAnchor.y + _popoutBodyBlurAnchor.height - _capHeight : _popoutBodyBlurAnchor.y) + width: _active ? _capWidth : 0 + height: _active ? _capHeight : 0 } Region { - item: _popoutLeftConnectorBlurAnchor + id: _popoutLeftConnectorBlurAnchor + + readonly property real _radius: win._popoutConnectorRadiusLeft + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "left", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _popoutLeftConnectorCutout + id: _popoutLeftConnectorCutout + + readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "left") + readonly property real _radius: win._popoutConnectorRadiusLeft + intersection: Intersection.Subtract radius: win._popoutConnectorRadiusLeft + x: _active ? Math.round(win._connectorCutoutX(_popoutLeftConnectorBlurAnchor.x, _popoutLeftConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_popoutLeftConnectorBlurAnchor.y, _popoutLeftConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _popoutRightConnectorBlurAnchor + id: _popoutRightConnectorBlurAnchor + + readonly property real _radius: win._popoutConnectorRadiusRight + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "right", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _popoutRightConnectorCutout + id: _popoutRightConnectorCutout + + readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutDescriptor.barSide, "right") + readonly property real _radius: win._popoutConnectorRadiusRight + intersection: Intersection.Subtract radius: win._popoutConnectorRadiusRight + x: _active ? Math.round(win._connectorCutoutX(_popoutRightConnectorBlurAnchor.x, _popoutRightConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_popoutRightConnectorBlurAnchor.y, _popoutRightConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _popoutFarStartBodyBlurCap + id: _popoutFarStartBodyBlurCap + + readonly property real _radius: win._effectivePopoutFarStartCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _popoutFarEndBodyBlurCap + id: _popoutFarEndBodyBlurCap + + readonly property real _radius: win._effectivePopoutFarEndCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _popoutFarStartConnectorBlurAnchor + id: _popoutFarStartConnectorBlurAnchor + + readonly property real _radius: win._effectivePopoutFarStartCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _popoutFarStartConnectorCutout + id: _popoutFarStartConnectorCutout + + readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectivePopoutFarStartCcr + x: _active ? Math.round(win._connectorCutoutX(_popoutFarStartConnectorBlurAnchor.x, _popoutFarStartConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_popoutFarStartConnectorBlurAnchor.y, _popoutFarStartConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _popoutFarEndConnectorBlurAnchor + id: _popoutFarEndConnectorBlurAnchor + + readonly property real _radius: win._effectivePopoutFarEndCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _popoutFarEndConnectorCutout + id: _popoutFarEndConnectorCutout + + readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectivePopoutFarEndCcr + x: _active ? Math.round(win._connectorCutoutX(_popoutFarEndConnectorBlurAnchor.x, _popoutFarEndConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_popoutFarEndConnectorBlurAnchor.y, _popoutFarEndConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _dockBodyBlurAnchor + id: _dockBodyBlurAnchor + + readonly property bool _active: win._blurSurfacesActive && win._connectedActive && win._dockDescriptor.visible && win._dockBodyGeometry.width > 0 && win._dockBodyGeometry.height > 0 + radius: win._dockBodyBlurRadiusValue + x: _active ? Math.round(win._dockBodyGeometry.x) : 0 + y: _active ? Math.round(win._dockBodyGeometry.y) : 0 + width: _active ? Math.round(win._dockBodyGeometry.width) : 0 + height: _active ? Math.round(win._dockBodyGeometry.height) : 0 } Region { - item: _dockBodyBlurCap + id: _dockBodyBlurCap + + 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 + + x: !_active ? 0 : (_side === "right" ? _dockBodyBlurAnchor.x + _dockBodyBlurAnchor.width - _capWidth : _dockBodyBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _dockBodyBlurAnchor.y + _dockBodyBlurAnchor.height - _capHeight : _dockBodyBlurAnchor.y) + width: _active ? _capWidth : 0 + height: _active ? _capHeight : 0 } Region { - item: _dockLeftConnectorBlurAnchor + id: _dockLeftConnectorBlurAnchor + + readonly property bool _active: _dockBodyBlurAnchor._active && win._dockConnectorRadiusValue > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._dockDescriptor.barSide, win._dockBodyGeometry, "left", 0, win._dockConnectorRadiusValue, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _dockLeftConnectorCutout + id: _dockLeftConnectorCutout + + readonly property bool _active: _dockLeftConnectorBlurAnchor.width > 0 && _dockLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "left") + intersection: Intersection.Subtract radius: win._dockConnectorRadiusValue + x: _active ? Math.round(win._connectorCutoutX(_dockLeftConnectorBlurAnchor.x, _dockLeftConnectorBlurAnchor.width, _arcCorner, win._dockConnectorRadiusValue)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_dockLeftConnectorBlurAnchor.y, _dockLeftConnectorBlurAnchor.height, _arcCorner, win._dockConnectorRadiusValue)) : 0 + width: _active ? Math.round(win._dockConnectorRadiusValue * 2) : 0 + height: _active ? Math.round(win._dockConnectorRadiusValue * 2) : 0 } } Region { - item: _dockRightConnectorBlurAnchor + id: _dockRightConnectorBlurAnchor + + readonly property bool _active: _dockBodyBlurAnchor._active && win._dockConnectorRadiusValue > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._dockDescriptor.barSide, win._dockBodyGeometry, "right", 0, win._dockConnectorRadiusValue, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _dockRightConnectorCutout + id: _dockRightConnectorCutout + + readonly property bool _active: _dockRightConnectorBlurAnchor.width > 0 && _dockRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._dockDescriptor.barSide, "right") + intersection: Intersection.Subtract radius: win._dockConnectorRadiusValue + x: _active ? Math.round(win._connectorCutoutX(_dockRightConnectorBlurAnchor.x, _dockRightConnectorBlurAnchor.width, _arcCorner, win._dockConnectorRadiusValue)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_dockRightConnectorBlurAnchor.y, _dockRightConnectorBlurAnchor.height, _arcCorner, win._dockConnectorRadiusValue)) : 0 + width: _active ? Math.round(win._dockConnectorRadiusValue * 2) : 0 + height: _active ? Math.round(win._dockConnectorRadiusValue * 2) : 0 } } Region { - item: _notifBodySceneBlurAnchor + id: _notifBodySceneBlurAnchor + + readonly property bool _active: _notifBodyBlurAnchor._active + readonly property var _scene: _active ? win._notifBodyScene() : null + radius: win._surfaceRadius + x: _scene ? Math.round(_scene.x) : 0 + y: _scene ? Math.round(_scene.y) : 0 + width: _scene ? Math.round(_scene.width) : 0 + height: _scene ? Math.round(_scene.height) : 0 } Region { - item: _notifBodyBlurCap + id: _notifBodyBlurCap + + 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 + readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(_capRadius, _notifBodySceneBlurAnchor.height)) : _notifBodySceneBlurAnchor.height + + x: !_active ? 0 : (_side === "right" ? _notifBodySceneBlurAnchor.x + _notifBodySceneBlurAnchor.width - _capWidth : _notifBodySceneBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _notifBodySceneBlurAnchor.y + _notifBodySceneBlurAnchor.height - _capHeight : _notifBodySceneBlurAnchor.y) + width: _active ? _capWidth : 0 + height: _active ? _capHeight : 0 } Region { - item: _notifLeftConnectorBlurAnchor + id: _notifLeftConnectorBlurAnchor + + readonly property real _radius: win._notifConnectorRadiusLeft + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "left", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _notifLeftConnectorCutout + id: _notifLeftConnectorCutout + + readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "left") + readonly property real _radius: win._notifConnectorRadiusLeft + intersection: Intersection.Subtract radius: win._notifConnectorRadiusLeft + x: _active ? Math.round(win._connectorCutoutX(_notifLeftConnectorBlurAnchor.x, _notifLeftConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_notifLeftConnectorBlurAnchor.y, _notifLeftConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _notifRightConnectorBlurAnchor + id: _notifRightConnectorBlurAnchor + + readonly property real _radius: win._notifConnectorRadiusRight + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "right", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _notifRightConnectorCutout + id: _notifRightConnectorCutout + + readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._notifDescriptor.barSide, "right") + readonly property real _radius: win._notifConnectorRadiusRight + intersection: Intersection.Subtract radius: win._notifConnectorRadiusRight + x: _active ? Math.round(win._connectorCutoutX(_notifRightConnectorBlurAnchor.x, _notifRightConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_notifRightConnectorBlurAnchor.y, _notifRightConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _notifFarStartBodyBlurCap + id: _notifFarStartBodyBlurCap + + readonly property real _radius: win._effectiveNotifFarStartCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _notifFarEndBodyBlurCap + id: _notifFarEndBodyBlurCap + + readonly property real _radius: win._effectiveNotifFarEndCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _notifFarStartConnectorBlurAnchor + id: _notifFarStartConnectorBlurAnchor + + readonly property real _radius: win._effectiveNotifFarStartCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _notifFarStartConnectorCutout + id: _notifFarStartConnectorCutout + + readonly property bool _active: _notifFarStartConnectorBlurAnchor.width > 0 && _notifFarStartConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectiveNotifFarStartCcr + x: _active ? Math.round(win._connectorCutoutX(_notifFarStartConnectorBlurAnchor.x, _notifFarStartConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_notifFarStartConnectorBlurAnchor.y, _notifFarStartConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _notifFarEndConnectorBlurAnchor + id: _notifFarEndConnectorBlurAnchor + + readonly property real _radius: win._effectiveNotifFarEndCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _notifFarEndConnectorCutout + id: _notifFarEndConnectorCutout + + readonly property bool _active: _notifFarEndConnectorBlurAnchor.width > 0 && _notifFarEndConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectiveNotifFarEndCcr + x: _active ? Math.round(win._connectorCutoutX(_notifFarEndConnectorBlurAnchor.x, _notifFarEndConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_notifFarEndConnectorBlurAnchor.y, _notifFarEndConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _modalBodyBlurAnchor + id: _modalBodyBlurAnchor + + readonly property bool _active: win._blurSurfacesActive && win._modalDescriptor.visible && win._modalBodyGeometry.width > 0 && win._modalBodyGeometry.height > 0 + radius: win._surfaceRadius + x: _active ? Math.round(win._modalBodyGeometry.x) : 0 + y: _active ? Math.round(win._modalBodyGeometry.y) : 0 + width: _active ? Math.round(win._modalBodyGeometry.width) : 0 + height: _active ? Math.round(win._modalBodyGeometry.height) : 0 } Region { - item: _modalBodyBlurCap + id: _modalBodyBlurCap + + 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 + readonly property int _capHeight: (_side === "top" || _side === "bottom") ? Math.round(Math.min(_capThickness, _modalBodyBlurAnchor.height)) : _modalBodyBlurAnchor.height + + x: !_active ? 0 : (_side === "right" ? _modalBodyBlurAnchor.x + _modalBodyBlurAnchor.width - _capWidth : _modalBodyBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _modalBodyBlurAnchor.y + _modalBodyBlurAnchor.height - _capHeight : _modalBodyBlurAnchor.y) + width: _active ? _capWidth : 0 + height: _active ? _capHeight : 0 } Region { - item: _modalLeftConnectorBlurAnchor + id: _modalLeftConnectorBlurAnchor + + readonly property real _radius: win._modalConnectorRadiusLeft + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "left", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _modalLeftConnectorCutout + id: _modalLeftConnectorCutout + + readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "left") + readonly property real _radius: win._modalConnectorRadiusLeft + intersection: Intersection.Subtract radius: win._modalConnectorRadiusLeft + x: _active ? Math.round(win._connectorCutoutX(_modalLeftConnectorBlurAnchor.x, _modalLeftConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_modalLeftConnectorBlurAnchor.y, _modalLeftConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _modalRightConnectorBlurAnchor + id: _modalRightConnectorBlurAnchor + + readonly property real _radius: win._modalConnectorRadiusRight + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.connectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "right", 0, _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _modalRightConnectorCutout + id: _modalRightConnectorCutout + + readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._modalDescriptor.barSide, "right") + readonly property real _radius: win._modalConnectorRadiusRight + intersection: Intersection.Subtract radius: win._modalConnectorRadiusRight + x: _active ? Math.round(win._connectorCutoutX(_modalRightConnectorBlurAnchor.x, _modalRightConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_modalRightConnectorBlurAnchor.y, _modalRightConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _modalFarStartBodyBlurCap + id: _modalFarStartBodyBlurCap + + readonly property real _radius: win._effectiveModalFarStartCcr + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _modalFarEndBodyBlurCap + id: _modalFarEndBodyBlurCap + + readonly property real _radius: win._effectiveModalFarEndCcr + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farBodyCapRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 } Region { - item: _modalFarStartConnectorBlurAnchor + id: _modalFarStartConnectorBlurAnchor + + readonly property real _radius: win._effectiveModalFarStartCcr + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "left", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _modalFarStartConnectorCutout + id: _modalFarStartConnectorCutout + + readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectiveModalFarStartCcr + x: _active ? Math.round(win._connectorCutoutX(_modalFarStartConnectorBlurAnchor.x, _modalFarStartConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_modalFarStartConnectorBlurAnchor.y, _modalFarStartConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } Region { - item: _modalFarEndConnectorBlurAnchor + id: _modalFarEndConnectorBlurAnchor + + readonly property real _radius: win._effectiveModalFarEndCcr + readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0 + readonly property var _rect: SurfaceGeometry.farConnectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "right", _radius, win._dpr) + + x: _active ? Math.round(_rect.x) : 0 + y: _active ? Math.round(_rect.y) : 0 + width: _active ? Math.round(_rect.width) : 0 + height: _active ? Math.round(_rect.height) : 0 + Region { - item: _modalFarEndConnectorCutout + id: _modalFarEndConnectorCutout + + readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0 + 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 + intersection: Intersection.Subtract radius: win._effectiveModalFarEndCcr + x: _active ? Math.round(win._connectorCutoutX(_modalFarEndConnectorBlurAnchor.x, _modalFarEndConnectorBlurAnchor.width, _arcCorner, _radius)) : 0 + y: _active ? Math.round(win._connectorCutoutY(_modalFarEndConnectorBlurAnchor.y, _modalFarEndConnectorBlurAnchor.height, _arcCorner, _radius)) : 0 + width: _active ? Math.round(_radius * 2) : 0 + height: _active ? Math.round(_radius * 2) : 0 } } } - // 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; @@ -993,7 +819,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 @@ -1005,131 +831,70 @@ PanelWindow { return Math.max(0, Math.min(win._effectiveModalCcr, extent - win._surfaceRadius)); } - function _popoutArcVisible() { - if (!_popoutBodyBlurAnchor._active || _popoutBodyBlurAnchor.width <= 0 || _popoutBodyBlurAnchor.height <= 0) - return false; - return win._popoutArcExtent >= win._ccr * (1 + win._ccr * 0.02); - } - function _popoutBlurCapThickness() { const extent = win._popoutArcExtent; return Math.max(0, Math.min(win._effectivePopoutMaxCcr, extent - win._surfaceRadius)); } - function _popoutChromeX() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyX - ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr : 0); - } - - function _popoutChromeY() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyY - ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr : 0); - } - - function _popoutChromeWidth() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyW + ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); - } - - function _popoutChromeHeight() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyH + ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); - } - - function _popoutClipX() { - return _popoutBodyBlurAnchor.x - win._popoutChromeX() - win._popoutFillOverlapXValue; - } - - function _popoutClipY() { - return _popoutBodyBlurAnchor.y - win._popoutChromeY() - win._popoutFillOverlapYValue; - } - - function _popoutClipWidth() { - return _popoutBodyBlurAnchor.width + win._popoutFillOverlapXValue * 2; - } - - function _popoutClipHeight() { - return _popoutBodyBlurAnchor.height + win._popoutFillOverlapYValue * 2; - } - - function _popoutShapeBodyOffsetX() { - const side = ConnectedModeState.popoutBarSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._effectivePopoutStartCcr; - return side === "right" ? win._effectivePopoutFarExtent : 0; - } - - function _popoutShapeBodyOffsetY() { - const side = ConnectedModeState.popoutBarSide; - if (ConnectorGeometry.isHorizontal(side)) - return side === "bottom" ? win._effectivePopoutFarExtent : 0; - return win._effectivePopoutStartCcr; - } - - function _popoutShapeWidth() { - const side = ConnectedModeState.popoutBarSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._popoutClipWidth() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; - return win._popoutClipWidth() + win._effectivePopoutFarExtent; - } - - function _popoutShapeHeight() { - const side = ConnectedModeState.popoutBarSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._popoutClipHeight() + win._effectivePopoutFarExtent; - return win._popoutClipHeight() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; - } - - function _popoutBodyXInClip() { - return (ConnectedModeState.popoutBarSide === "left" ? _popoutBodyBlurAnchor._dxClamp : 0) - win._popoutFillOverlapXValue; - } - - function _popoutBodyYInClip() { - return (ConnectedModeState.popoutBarSide === "top" ? _popoutBodyBlurAnchor._dyClamp : 0) - win._popoutFillOverlapYValue; - } - - function _popoutBodyFullWidth() { - return ConnectedModeState.popoutBodyW + win._popoutFillOverlapXValue * 2; - } - - function _popoutBodyFullHeight() { - return ConnectedModeState.popoutBodyH + win._popoutFillOverlapYValue * 2; - } - - function _dockChromeX() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.x - ((dockSide === "top" || dockSide === "bottom") ? win._dockConnectorRadiusValue : 0); - } - - function _dockChromeY() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.y - ((dockSide === "left" || dockSide === "right") ? win._dockConnectorRadiusValue : 0); - } - - function _dockChromeWidth() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.width + ((dockSide === "top" || dockSide === "bottom") ? win._dockConnectorRadiusValue * 2 : 0); - } - - function _dockChromeHeight() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.height + ((dockSide === "left" || dockSide === "right") ? win._dockConnectorRadiusValue * 2 : 0); - } - - function _dockBodyXInChrome() { - return (ConnectorGeometry.isHorizontal(win._dockState.barSide) ? win._dockConnectorRadiusValue : 0) - win._dockFillOverlapXValue; - } - - function _dockBodyYInChrome() { - return ((win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._dockConnectorRadiusValue : 0) - win._dockFillOverlapYValue; - } - - function _dockJoinOverlapXOffset() { - return win._dockState.barSide === "left" ? -win._dockJoinOverlapXValue : 0; - } - - function _dockJoinOverlapYOffset() { - return win._dockState.barSide === "top" ? -win._dockJoinOverlapYValue : 0; + function _unifiedSurfaces() { + const arr = []; + const p = win._popoutBodyGeometry; + 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}, + "radii": { + "farCr": win._effectivePopoutFarCcr, + "startCr": win._effectivePopoutStartCcr, + "endCr": win._effectivePopoutEndCcr, + "farStartCr": win._effectivePopoutFarStartCcr, + "farEndCr": win._effectivePopoutFarEndCcr, + "surfaceRadius": win._surfaceRadius + } + }); + const m = win._modalBodyGeometry; + if (win._frameActive && win._modalDescriptor.visible && m.width > 0 && m.height > 0) + arr.push({ + "side": win._modalDescriptor.barSide, + "body": {"x": m.x, "y": m.y, "width": m.width, "height": m.height}, + "radii": { + "farCr": win._effectiveModalFarCcr, + "startCr": win._effectiveModalStartCcr, + "endCr": win._effectiveModalEndCcr, + "farStartCr": win._effectiveModalFarStartCcr, + "farEndCr": win._effectiveModalFarEndCcr, + "surfaceRadius": win._surfaceRadius + } + }); + const n = win._notifBodyGeometry; + if (win._frameActive && win._notifDescriptor.visible && n.width > 0 && n.height > 0) + arr.push({ + "side": win._notifDescriptor.barSide, + "body": {"x": n.x, "y": n.y, "width": n.width, "height": n.height}, + "radii": { + "farCr": win._effectiveNotifFarCcr, + "startCr": win._effectiveNotifStartCcr, + "endCr": win._effectiveNotifEndCcr, + "farStartCr": win._effectiveNotifFarStartCcr, + "farEndCr": win._effectiveNotifFarEndCcr, + "surfaceRadius": win._surfaceRadius + } + }); + const dk = win._dockBodyGeometry; + if (win._connectedActive && win._dockDescriptor.visible && dk.width > 0 && dk.height > 0) + arr.push({ + "side": win._dockDescriptor.barSide, + "body": {"x": dk.x, "y": dk.y, "width": dk.width, "height": dk.height}, + "radii": { + "farCr": win._dockConnectorRadiusValue, + "startCr": win._dockConnectorRadiusValue, + "endCr": win._dockConnectorRadiusValue, + "farStartCr": 0, + "farEndCr": 0, + "surfaceRadius": win._dockBodyBlurRadiusValue + } + }); + return arr; } function _farConnectorBarSide(sourceSide, placement) { @@ -1148,38 +913,6 @@ PanelWindow { return "left"; } - function _farConnectorX(baseX, baseY, bodyWidth, bodyHeight, sourceSide, placement, radius) { - if (sourceSide === "top" || sourceSide === "bottom") - return placement === "left" ? baseX : baseX + bodyWidth - radius; - if (sourceSide === "left") - return baseX + bodyWidth; - return baseX - radius; - } - - function _farConnectorY(baseX, baseY, bodyWidth, bodyHeight, sourceSide, placement, radius) { - if (sourceSide === "top") - return baseY + bodyHeight; - if (sourceSide === "bottom") - return baseY - radius; - return placement === "left" ? baseY : baseY + bodyHeight - radius; - } - - function _farBodyCapX(baseX, bodyWidth, sourceSide, placement, radius) { - if (sourceSide === "top" || sourceSide === "bottom") - return placement === "left" ? baseX : baseX + bodyWidth - radius; - if (sourceSide === "left") - return baseX + bodyWidth - radius; - return baseX; - } - - function _farBodyCapY(baseY, bodyHeight, sourceSide, placement, radius) { - if (sourceSide === "top") - return baseY + bodyHeight - radius; - if (sourceSide === "bottom") - return baseY; - return placement === "left" ? baseY : baseY + bodyHeight - radius; - } - function _connectorCutoutX(connectorX, connectorWidth, arcCorner, radius) { const r = radius === undefined ? win._effectivePopoutCcr : radius; return (arcCorner === "topLeft" || arcCorner === "bottomLeft") ? connectorX - r : connectorX + connectorWidth - r; @@ -1210,8 +943,6 @@ PanelWindow { } catch (e) {} } - // Coalesce bursts of settings-change signals into a single _buildBlur() call - // on the next event loop tick. DeferredAction { id: blurRebuildAction onTriggered: win._runBlurRebuild() @@ -1235,28 +966,13 @@ PanelWindow { } catch (e) {} } - function _scheduleSurfaceRefresh(recreateLayer) { - if (recreateLayer) - _surfaceRefreshNeedsLayerRecreate = true; + function _scheduleSurfaceRefresh() { surfaceRefreshAction.restart(); } function _runSurfaceRefresh() { if (!win.visible) return; - if (_surfaceRefreshNeedsLayerRecreate) { - _surfaceRefreshNeedsLayerRecreate = false; - if (win._connectedActive && !win._disableLayer && (Theme.elevationEnabled || win._surfaceOpacity < 1)) { - _surfaceLayerRecoveryActive = true; - surfaceLayerRestoreAction.restart(); - } - } - _requestContentUpdate(); - _republishFrameBlur(); - } - - function _finishSurfaceLayerRecovery() { - _surfaceLayerRecoveryActive = false; _requestContentUpdate(); _republishFrameBlur(); } @@ -1266,11 +982,6 @@ PanelWindow { onTriggered: win._runSurfaceRefresh() } - DeferredAction { - id: surfaceLayerRestoreAction - onTriggered: win._finishSurfaceLayerRecovery() - } - Connections { target: SettingsData function onFrameBlurEnabledChanged() { @@ -1315,41 +1026,33 @@ PanelWindow { onVisibleChanged: { if (visible) { win._scheduleBlurRebuild(); - win._scheduleSurfaceRefresh(false); + win._scheduleSurfaceRefresh(); } else { surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); - _surfaceLayerRecoveryActive = false; - _surfaceRefreshNeedsLayerRecreate = false; _teardownBlur(); } } - on_SurfaceRevisionChanged: win._scheduleSurfaceRefresh(false) + on_SurfaceRevisionChanged: win._scheduleSurfaceRefresh() onResourcesLost: { blurRebuildAction.cancel(); surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); - _surfaceRefreshNeedsLayerRecreate = true; - if (win._connectedActive && !win._disableLayer && (Theme.elevationEnabled || win._surfaceOpacity < 1)) - _surfaceLayerRecoveryActive = true; win._teardownBlur(); } onWindowConnected: { - win._scheduleSurfaceRefresh(true); + win._scheduleSurfaceRefresh(); win._scheduleBlurRebuild(); } Component.onCompleted: { win._scheduleBlurRebuild(); - win._scheduleSurfaceRefresh(false); + win._scheduleSurfaceRefresh(); } Component.onDestruction: { blurRebuildAction.cancel(); surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); win._teardownBlur(); } @@ -1363,227 +1066,37 @@ PanelWindow { cutoutRadius: win.cutoutRadius } - Item { - id: _connectedSurfaceLayer + ShaderEffect { anchors.fill: parent visible: win._connectedActive - opacity: win._surfaceOpacity - // Skip FBO when disabled, invisible, or when neither elevation nor alpha blend is active - layer.enabled: win._connectedActive && !win._surfaceLayerRecoveryActive && !win._disableLayer && (Theme.elevationEnabled || win._surfaceOpacity < 1) - layer.smooth: false + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_arc.frag.qsb") - layer.effect: MultiEffect { - readonly property var level: Theme.elevationLevel2 - readonly property real _shadowBlur: Theme.elevationEnabled ? (level && level.blurPx !== undefined ? level.blurPx : 0) : 0 - readonly property real _shadowSpread: Theme.elevationEnabled ? (level && level.spreadPx !== undefined ? level.spreadPx : 0) : 0 - - autoPaddingEnabled: true - blurEnabled: false - maskEnabled: false - - shadowEnabled: !win._disableLayer && Theme.elevationEnabled - shadowBlur: Math.max(0, Math.min(1, _shadowBlur / Math.max(1, Theme.elevationBlurMax))) - shadowScale: 1 + (2 * _shadowSpread) / Math.max(1, Math.min(_connectedSurfaceLayer.width, _connectedSurfaceLayer.height)) - shadowHorizontalOffset: Theme.elevationOffsetXFor(level, Theme.elevationLightDirection, 4) - shadowVerticalOffset: Theme.elevationOffsetYFor(level, Theme.elevationLightDirection, 4) - shadowColor: Theme.elevationShadowColor(level) - shadowOpacity: 1 - } - - FrameBorder { - anchors.fill: parent - borderColor: win._opaqueSurfaceColor - cutoutTopInset: win.cutoutTopInset - cutoutBottomInset: win.cutoutBottomInset - cutoutLeftInset: win.cutoutLeftInset - cutoutRightInset: win.cutoutRightInset - cutoutRadius: win.cutoutRadius - } - - Item { - id: _connectedChrome - anchors.fill: parent - visible: win._connectedActive - - Item { - id: _popoutChrome - visible: ConnectedModeState.popoutVisible && ConnectedModeState.popoutScreen === win._screenName - x: win._popoutChromeX() - y: win._popoutChromeY() - width: win._popoutChromeWidth() - height: win._popoutChromeHeight() - - Item { - id: _popoutClip - x: win._popoutClipX() - win._popoutShapeBodyOffsetX() - y: win._popoutClipY() - win._popoutShapeBodyOffsetY() - width: win._popoutShapeWidth() - height: win._popoutShapeHeight() - clip: true - - ConnectedShape { - id: _popoutShape - visible: _popoutBodyBlurAnchor._active && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0 - barSide: ConnectedModeState.popoutBarSide - bodyWidth: win._popoutClipWidth() - bodyHeight: win._popoutClipHeight() - connectorRadius: win._effectivePopoutCcr - startConnectorRadius: win._effectivePopoutStartCcr - endConnectorRadius: win._effectivePopoutEndCcr - farStartConnectorRadius: win._effectivePopoutFarStartCcr - farEndConnectorRadius: win._effectivePopoutFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - } - - Item { - id: _dockChrome - visible: _dockBodyBlurAnchor._active - x: win._dockChromeX() - y: win._dockChromeY() - width: win._dockChromeWidth() - height: win._dockChromeHeight() - - Rectangle { - id: _dockFill - x: win._dockBodyXInChrome() + win._dockJoinOverlapXOffset() - y: win._dockBodyYInChrome() + win._dockJoinOverlapYOffset() - width: _dockBodyBlurAnchor.width + win._dockFillOverlapXValue * 2 + win._dockJoinOverlapXValue - height: _dockBodyBlurAnchor.height + win._dockFillOverlapYValue * 2 + win._dockJoinOverlapYValue - color: win._opaqueSurfaceColor - z: 1 - - readonly property string _dockSide: win._dockState.barSide - readonly property real _dockRadius: win._dockBodyBlurRadiusValue - topLeftRadius: (_dockSide === "top" || _dockSide === "left") ? 0 : _dockRadius - topRightRadius: (_dockSide === "top" || _dockSide === "right") ? 0 : _dockRadius - bottomLeftRadius: (_dockSide === "bottom" || _dockSide === "left") ? 0 : _dockRadius - bottomRightRadius: (_dockSide === "bottom" || _dockSide === "right") ? 0 : _dockRadius - } - - ConnectedCorner { - id: _connDockLeft - visible: _dockBodyBlurAnchor._active - barSide: win._dockState.barSide - placement: "left" - spacing: 0 - connectorRadius: win._dockConnectorRadiusValue - color: win._opaqueSurfaceColor - dpr: win._dpr - x: Theme.snap(ConnectorGeometry.connectorX(win._dockState.barSide, _dockBodyBlurAnchor.x, _dockBodyBlurAnchor.width, "left", 0, win._dockConnectorRadiusValue) - _dockChrome.x, win._dpr) - y: Theme.snap(ConnectorGeometry.connectorY(win._dockState.barSide, _dockBodyBlurAnchor.y, _dockBodyBlurAnchor.height, "left", 0, win._dockConnectorRadiusValue) - _dockChrome.y, win._dpr) - } - - ConnectedCorner { - id: _connDockRight - visible: _dockBodyBlurAnchor._active - barSide: win._dockState.barSide - placement: "right" - spacing: 0 - connectorRadius: win._dockConnectorRadiusValue - color: win._opaqueSurfaceColor - dpr: win._dpr - x: Theme.snap(ConnectorGeometry.connectorX(win._dockState.barSide, _dockBodyBlurAnchor.x, _dockBodyBlurAnchor.width, "right", 0, win._dockConnectorRadiusValue) - _dockChrome.x, win._dpr) - y: Theme.snap(ConnectorGeometry.connectorY(win._dockState.barSide, _dockBodyBlurAnchor.y, _dockBodyBlurAnchor.height, "right", 0, win._dockConnectorRadiusValue) - _dockChrome.y, win._dpr) - } - } - } - - Item { - id: _notifChrome - visible: _notifBodySceneBlurAnchor._active - - readonly property string _notifSide: win._notifState.barSide - readonly property bool _isHoriz: _notifSide === "top" || _notifSide === "bottom" - readonly property real _startCcr: win._effectiveNotifStartCcr - readonly property real _endCcr: win._effectiveNotifEndCcr - readonly property real _farExtent: win._effectiveNotifFarExtent - readonly property real _bodyOffsetX: _isHoriz ? _startCcr : (_notifSide === "right" ? _farExtent : 0) - readonly property real _bodyOffsetY: _isHoriz ? (_notifSide === "bottom" ? _farExtent : 0) : _startCcr - readonly property real _bodyW: Theme.snap(_notifBodySceneBlurAnchor.width, win._dpr) - readonly property real _bodyH: Theme.snap(_notifBodySceneBlurAnchor.height, win._dpr) - - z: _isHoriz ? 0 : -1 - x: Theme.snap(_notifBodySceneBlurAnchor.x - _bodyOffsetX, win._dpr) - y: Theme.snap(_notifBodySceneBlurAnchor.y - _bodyOffsetY, win._dpr) - width: _isHoriz ? Theme.snap(_bodyW + _startCcr + _endCcr, win._dpr) : Theme.snap(_bodyW + _farExtent, win._dpr) - height: _isHoriz ? Theme.snap(_bodyH + _farExtent, win._dpr) : Theme.snap(_bodyH + _startCcr + _endCcr, win._dpr) - - ConnectedShape { - visible: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 - barSide: _notifChrome._notifSide - bodyWidth: _notifChrome._bodyW - bodyHeight: _notifChrome._bodyH - connectorRadius: win._effectiveNotifCcr - startConnectorRadius: _notifChrome._startCcr - endConnectorRadius: _notifChrome._endCcr - farStartConnectorRadius: win._effectiveNotifFarStartCcr - farEndConnectorRadius: win._effectiveNotifFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - - // Bar-side-bounded clip so modal chrome retracts behind the bar on exit - // instead of sliding over bar widgets (mirrors the popout `_popoutClip`). - Item { - id: _modalClip - visible: _modalBodyBlurAnchor._active - z: 1 - - readonly property string _modalSide: win._modalState.barSide - readonly property real _inset: _modalBodyBlurAnchor._active && win.screen ? SettingsData.frameEdgeInsetForSide(win.screen, _modalSide) : 0 - readonly property real _topBound: _modalSide === "top" ? _inset : 0 - readonly property real _bottomBound: _modalSide === "bottom" ? (win.height - _inset) : win.height - readonly property real _leftBound: _modalSide === "left" ? _inset : 0 - readonly property real _rightBound: _modalSide === "right" ? (win.width - _inset) : win.width - - x: _leftBound - y: _topBound - width: Math.max(0, _rightBound - _leftBound) - height: Math.max(0, _bottomBound - _topBound) - clip: true - - Item { - id: _modalChrome - - readonly property string _modalSide: win._modalState.barSide - readonly property bool _isHoriz: _modalSide === "top" || _modalSide === "bottom" - readonly property real _startCcr: win._effectiveModalStartCcr - readonly property real _endCcr: win._effectiveModalEndCcr - readonly property real _farExtent: win._effectiveModalFarExtent - readonly property real _bodyOffsetX: _isHoriz ? _startCcr : (_modalSide === "right" ? _farExtent : 0) - readonly property real _bodyOffsetY: _isHoriz ? (_modalSide === "bottom" ? _farExtent : 0) : _startCcr - readonly property real _bodyW: Theme.snap(_modalBodyBlurAnchor.width, win._dpr) - readonly property real _bodyH: Theme.snap(_modalBodyBlurAnchor.height, win._dpr) - - x: Theme.snap(_modalBodyBlurAnchor.x - _bodyOffsetX - _modalClip.x, win._dpr) - y: Theme.snap(_modalBodyBlurAnchor.y - _bodyOffsetY - _modalClip.y, win._dpr) - width: _isHoriz ? Theme.snap(_bodyW + _startCcr + _endCcr, win._dpr) : Theme.snap(_bodyW + _farExtent, win._dpr) - height: _isHoriz ? Theme.snap(_bodyH + _farExtent, win._dpr) : Theme.snap(_bodyH + _startCcr + _endCcr, win._dpr) - - ConnectedShape { - visible: _modalBodyBlurAnchor._active && _modalChrome._bodyW > 0 && _modalChrome._bodyH > 0 - barSide: _modalChrome._modalSide - bodyWidth: _modalChrome._bodyW - bodyHeight: _modalChrome._bodyH - connectorRadius: win._effectiveModalCcr - startConnectorRadius: _modalChrome._startCcr - endConnectorRadius: _modalChrome._endCcr - farStartConnectorRadius: win._effectiveModalFarStartCcr - farEndConnectorRadius: win._effectiveModalFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - } + readonly property var _level: Theme.elevationLevel2 + readonly property color _shadowTint: Theme.elevationShadowColor(_level) + readonly property var _ambient: Theme.elevationAmbient(_level) + property real widthPx: width + property real heightPx: height + property real cutoutRadius: win.cutoutRadius + property vector4d cutout: Qt.vector4d(win.cutoutLeftInset, win.cutoutTopInset, win.width - win.cutoutRightInset, win.height - win.cutoutBottomInset) + property vector4d surfaceColor: Qt.vector4d(win._surfaceColor.r, win._surfaceColor.g, win._surfaceColor.b, win._surfaceColor.a) + property vector4d shadowColor: Qt.vector4d(_shadowTint.r, _shadowTint.g, _shadowTint.b, win._elevationShadow ? _shadowTint.a : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, _level.blurPx), Math.max(0, _level.spreadPx), Theme.elevationOffsetXFor(_level, Theme.elevationLightDirection, 4), Theme.elevationOffsetYFor(_level, Theme.elevationLightDirection, 4)) + property vector4d ambientParam: Qt.vector4d(_ambient.blurPx, _ambient.spreadPx, win._elevationShadow ? _ambient.alpha : 0, 0) + property vector4d chromeRect0: win._sdfSlots[0].rect + property vector4d chromeCorner0: win._sdfSlots[0].corner + property vector4d chromeK0: win._sdfSlots[0].k + property vector4d chromeParam0: win._sdfSlots[0].param + property vector4d chromeRect1: win._sdfSlots[1].rect + property vector4d chromeCorner1: win._sdfSlots[1].corner + property vector4d chromeK1: win._sdfSlots[1].k + property vector4d chromeParam1: win._sdfSlots[1].param + property vector4d chromeRect2: win._sdfSlots[2].rect + property vector4d chromeCorner2: win._sdfSlots[2].corner + property vector4d chromeK2: win._sdfSlots[2].k + property vector4d chromeParam2: win._sdfSlots[2].param + property vector4d chromeRect3: win._sdfSlots[3].rect + property vector4d chromeCorner3: win._sdfSlots[3].corner + property vector4d chromeK3: win._sdfSlots[3].k + property vector4d chromeParam3: win._sdfSlots[3].param } } diff --git a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml index be55f2e8..7b7cf43d 100644 --- a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml @@ -31,7 +31,7 @@ Rectangle { height: baseCardHeight + contentItem.extraHeight radius: Theme.cornerRadius clip: false - readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled + readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" ElevationShadow { id: shadowLayer diff --git a/quickshell/Modules/Notifications/Center/NotificationCard.qml b/quickshell/Modules/Notifications/Center/NotificationCard.qml index 22a3e285..9c8869ec 100644 --- a/quickshell/Modules/Notifications/Center/NotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/NotificationCard.qml @@ -47,7 +47,7 @@ Rectangle { readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) radius: connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence - readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled + readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" readonly property var shadowElevation: Theme.elevationLevel1 readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4 readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0 diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 8887bd50..acd897f2 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -641,21 +641,15 @@ PanelWindow { shadowOffsetY: content.shadowOffsetY shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent" shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode - layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically - sourceRect.anchors.fill: undefined - sourceRect.x: content.shadowRenderPadding + content.cardInset - sourceRect.y: content.shadowRenderPadding + content.cardInset - sourceRect.width: Math.max(0, content.width - (content.cardInset * 2)) - sourceRect.height: Math.max(0, content.height - (content.cardInset * 2)) - sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius - sourceRect.color: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface - sourceRect.antialiasing: true - sourceRect.layer.enabled: false - sourceRect.layer.textureSize: Qt.size(0, 0) - sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) - sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0 + sourceX: content.shadowRenderPadding + content.cardInset + sourceY: content.shadowRenderPadding + content.cardInset + sourceWidth: Math.max(0, content.width - (content.cardInset * 2)) + sourceHeight: Math.max(0, content.height - (content.cardInset * 2)) + targetRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius + targetColor: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface + borderColor: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) + borderWidth: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? 2 : 0 } // Keep critical accent outside shadow rendering so connected mode still shows it. diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml index 41094650..c9676296 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml @@ -513,13 +513,30 @@ QtObject { ConnectedModeState.clearNotificationState(screenName); return; } + const bodyRect = { + x: minX, + y: minY, + width: maxXEnd - minX, + height: maxYEnd - minY + }; ConnectedModeState.setNotificationState(screenName, { + kind: "notification", + screenName: screenName, + phase: "open", visible: true, + presented: true, barSide: notifBarSide, + bodyRect: bodyRect, + animationOffset: { + x: 0, + y: 0 + }, + scale: 1, + opacity: Theme.connectedSurfaceColor.a, bodyX: minX, bodyY: minY, - bodyW: maxXEnd - minX, - bodyH: maxYEnd - minY, + bodyW: bodyRect.width, + bodyH: bodyRect.height, omitStartConnector: _notificationOmitStartConnector(), omitEndConnector: _notificationOmitEndConnector() }); diff --git a/quickshell/Modules/Plugins/PluginComponent.qml b/quickshell/Modules/Plugins/PluginComponent.qml index 85dd32da..39d9ff68 100644 --- a/quickshell/Modules/Plugins/PluginComponent.qml +++ b/quickshell/Modules/Plugins/PluginComponent.qml @@ -23,6 +23,7 @@ Item { property bool conditionVisible: true property bool _visibilityOverride: false property bool _visibilityOverrideValue: true + readonly property bool _barRevealed: blurBarWindow?.barRevealed ?? true readonly property bool effectiveVisible: { if (_visibilityOverride) @@ -122,6 +123,11 @@ Item { conditionVisible = true; } + on_BarRevealedChanged: { + if (_barRevealed && visibilityCommand && !_visibilityOverride) + checkVisibility(); + } + onVisibilityIntervalChanged: { if (visibilityInterval > 0 && visibilityCommand) { visibilityTimer.restart(); @@ -134,7 +140,7 @@ Item { id: visibilityTimer interval: root.visibilityInterval * 1000 repeat: true - running: root.visibilityInterval > 0 && root.visibilityCommand !== "" + running: root.visibilityInterval > 0 && root.visibilityCommand !== "" && root._barRevealed && !root._visibilityOverride onTriggered: root.checkVisibility() } diff --git a/quickshell/Shaders/frag/connected_arc.frag b/quickshell/Shaders/frag/connected_arc.frag new file mode 100644 index 00000000..be485683 --- /dev/null +++ b/quickshell/Shaders/frag/connected_arc.frag @@ -0,0 +1,110 @@ +#version 450 + +// Connected frame silhouette: frame ring + chrome bodies as one SDF with elevation shadow. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + float cutoutRadius; + vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom + vec4 surfaceColor; // straight (non-premultiplied) rgba + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha + // Up to four chrome slots. rect = x,y,w,h (px). corner = per-corner radii, + // k = per-corner junction fillet radii (both topLeft, topRight, bottomRight, + // bottomLeft; a corner is sharp exactly where its k > 0). param = active, 0, 0, 0 + vec4 chromeRect0; + vec4 chromeCorner0; + vec4 chromeK0; + vec4 chromeParam0; + vec4 chromeRect1; + vec4 chromeCorner1; + vec4 chromeK1; + vec4 chromeParam1; + vec4 chromeRect2; + vec4 chromeCorner2; + vec4 chromeK2; + vec4 chromeParam2; + vec4 chromeRect3; + vec4 chromeCorner3; + vec4 chromeK3; + vec4 chromeParam3; +} ubuf; + +float sdBox(vec2 p, vec2 c, vec2 hs) { + vec2 q = abs(p - c) - hs; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))); +} + +float sdRoundBox(vec2 p, vec2 c, vec2 hs, float r) { + r = min(r, min(hs.x, hs.y)); + vec2 q = abs(p - c) - hs + r; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r; +} + +float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) { + p -= c; + float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x); + rr = min(rr, min(hs.x, hs.y)); + vec2 q = abs(p) - hs + rr; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +float smin(float a, float b, float k) { + if (k <= 0.0) + return min(a, b); + return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0))); +} + +float chromeDist(vec2 px, vec4 rect, vec4 corner) { + vec2 c = rect.xy + rect.zw * 0.5; + return sdRoundBox4(px, c, rect.zw * 0.5, corner); +} + +float chromeK(vec2 px, vec4 rect, vec4 ks) { + vec2 p = px - (rect.xy + rect.zw * 0.5); + return (p.x >= 0.0) ? (p.y >= 0.0 ? ks.z : ks.y) : (p.y >= 0.0 ? ks.w : ks.x); +} + +float sceneDist(vec2 px) { + vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5; + float dOuter = sdBox(px, sc, sc); + vec2 cutC = vec2((ubuf.cutout.x + ubuf.cutout.z) * 0.5, (ubuf.cutout.y + ubuf.cutout.w) * 0.5); + vec2 cutH = vec2((ubuf.cutout.z - ubuf.cutout.x) * 0.5, (ubuf.cutout.w - ubuf.cutout.y) * 0.5); + float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius); + float d = max(dOuter, -dCut); + + if (ubuf.chromeParam0.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect0, ubuf.chromeCorner0), chromeK(px, ubuf.chromeRect0, ubuf.chromeK0)); + if (ubuf.chromeParam1.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect1, ubuf.chromeCorner1), chromeK(px, ubuf.chromeRect1, ubuf.chromeK1)); + if (ubuf.chromeParam2.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect2, ubuf.chromeCorner2), chromeK(px, ubuf.chromeRect2, ubuf.chromeK2)); + if (ubuf.chromeParam3.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect3, ubuf.chromeCorner3), chromeK(px, ubuf.chromeRect3, ubuf.chromeK3)); + return d; +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = sceneDist(px); + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov; + if (ubuf.shadowColor.a > 0.0) { + float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a)); + } + fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity); +} diff --git a/quickshell/Shaders/frag/connected_chrome.frag b/quickshell/Shaders/frag/connected_chrome.frag new file mode 100644 index 00000000..c106972f --- /dev/null +++ b/quickshell/Shaders/frag/connected_chrome.frag @@ -0,0 +1,63 @@ +#version 450 + +// Popout-local connected chrome body + bar-edge connector as one SDF. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + vec4 surfaceColor; // straight (non-premultiplied) rgba + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha + vec4 bodyRect; // body rounded rect in item px: x, y, w, h + vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft + vec4 edgeParam; // x = bar side (0 top, 1 bottom, 2 left, 3 right), y = fillet k +} ubuf; + +float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) { + p -= c; + float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x); + rr = min(rr, min(hs.x, hs.y)); + vec2 q = abs(p) - hs + rr; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +float smin(float a, float b, float k) { + if (k <= 0.0) + return min(a, b); + return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0))); +} + +float sceneDist(vec2 px) { + float side = ubuf.edgeParam.x; + float dEdge = side < 0.5 ? px.y + : side < 1.5 ? (ubuf.heightPx - px.y) + : side < 2.5 ? px.x + : (ubuf.widthPx - px.x); + vec2 hs = ubuf.bodyRect.zw * 0.5; + float dBody = sdRoundBox4(px, ubuf.bodyRect.xy + hs, hs, ubuf.cornerRadius); + return smin(dEdge, dBody, ubuf.edgeParam.y); +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = sceneDist(px); + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov; + if (ubuf.shadowColor.a > 0.0) { + float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a)); + } + fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity); +} diff --git a/quickshell/Shaders/frag/elevation_rect.frag b/quickshell/Shaders/frag/elevation_rect.frag new file mode 100644 index 00000000..d211eaec --- /dev/null +++ b/quickshell/Shaders/frag/elevation_rect.frag @@ -0,0 +1,54 @@ +#version 450 + +// Standalone rounded rect with border and M3 elevation shadow as one SDF. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + float borderWidth; + vec4 rectPx; // rounded rect in item px: x, y, w, h + vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft + vec4 fillColor; // straight (non-premultiplied) rgba + vec4 borderColor; // straight rgba + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha +} ubuf; + +float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) { + p -= c; + float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x); + rr = min(rr, min(hs.x, hs.y)); + vec2 q = abs(p) - hs + rr; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +float rectDist(vec2 px) { + vec2 hs = ubuf.rectPx.zw * 0.5; + return sdRoundBox4(px, ubuf.rectPx.xy + hs, hs, ubuf.cornerRadius); +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = rectDist(px); + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + float covInner = 1.0 - smoothstep(-fw, fw, d + ubuf.borderWidth); + vec4 col = vec4(ubuf.fillColor.rgb, 1.0) * (ubuf.fillColor.a * covInner) + + vec4(ubuf.borderColor.rgb, 1.0) * (ubuf.borderColor.a * max(0.0, cov - covInner)); + if (ubuf.shadowColor.a > 0.0) { + float dk = rectDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - cov)); + } + fragColor = col * ubuf.qt_Opacity; +} diff --git a/quickshell/Shaders/frag/frame_arc.frag b/quickshell/Shaders/frag/frame_arc.frag new file mode 100644 index 00000000..b61975a0 --- /dev/null +++ b/quickshell/Shaders/frag/frame_arc.frag @@ -0,0 +1,42 @@ +#version 450 + +// Frame perimeter ring with rounded cutout as one SDF. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + float cutoutRadius; + vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom + vec4 surfaceColor; // straight (non-premultiplied) rgba +} ubuf; + +float sdBox(vec2 p, vec2 c, vec2 hs) { + vec2 q = abs(p - c) - hs; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))); +} + +float sdRoundBox(vec2 p, vec2 c, vec2 hs, float r) { + r = min(r, min(hs.x, hs.y)); + vec2 q = abs(p - c) - hs + r; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r; +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5; + float dOuter = sdBox(px, sc, sc); + vec2 cutC = vec2((ubuf.cutout.x + ubuf.cutout.z) * 0.5, (ubuf.cutout.y + ubuf.cutout.w) * 0.5); + vec2 cutH = vec2((ubuf.cutout.z - ubuf.cutout.x) * 0.5, (ubuf.cutout.w - ubuf.cutout.y) * 0.5); + float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius); + float d = max(dOuter, -dCut); + + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + float a = ubuf.surfaceColor.a * cov * ubuf.qt_Opacity; + fragColor = vec4(ubuf.surfaceColor.rgb * a, a); +} diff --git a/quickshell/Shaders/qsb/connected_arc.frag.qsb b/quickshell/Shaders/qsb/connected_arc.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..ee72cdf41242b5af5056f96190a47b6cbddfc402 GIT binary patch literal 7880 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 HgnJ&d_s{F8 literal 0 HcmV?d00001 diff --git a/quickshell/Shaders/qsb/elevation_rect.frag.qsb b/quickshell/Shaders/qsb/elevation_rect.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..01ddc47ce4a8d47e95f5c94aabd42dafbcf56427 GIT binary patch literal 4215 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 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/DankOSD.qml b/quickshell/Widgets/DankOSD.qml index 284be7ff..481af559 100644 --- a/quickshell/Widgets/DankOSD.qml +++ b/quickshell/Widgets/DankOSD.qml @@ -289,8 +289,6 @@ PanelWindow { borderColor: Theme.outlineMedium borderWidth: 1 shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" - layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically } MouseArea { diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 117b28fc..1fc6c347 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell.Hyprland import qs.Common import qs.Services @@ -50,6 +51,20 @@ Item { readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null + readonly property var contentWindow: impl.item ? impl.item.contentWindow : null + + // Hyprland OnDemand grab: whitelist popout surfaces and bars so dismiss clicks still land. + HyprlandFocusGrab { + windows: { + const list = []; + if (root.contentWindow) + list.push(root.contentWindow); + if (root.backgroundWindow && root.backgroundWindow !== root.contentWindow) + list.push(root.backgroundWindow); + return list.concat(KeyboardFocus.barWindows); + } + active: KeyboardFocus.wantsGrab(root.shouldBeVisible, root.customKeyboardFocus) + } Loader { id: _fallbackContentLoader @@ -127,8 +142,6 @@ Item { return _usesConnectedBackendForScreen(targetScreen) ? connectedComp : standaloneComp; } - // Defer Loader source-component swap until impl is fully closed; avoids - // tearing down a popout mid-animation when frame mode is toggled. function _maybeResolveBackend() { _resolveBackendForScreen(screen); } diff --git a/quickshell/Widgets/DankPopoutConnected.qml b/quickshell/Widgets/DankPopoutConnected.qml index 9f7a2f54..3d88a5ce 100644 --- a/quickshell/Widgets/DankPopoutConnected.qml +++ b/quickshell/Widgets/DankPopoutConnected.qml @@ -5,7 +5,6 @@ import Quickshell import Quickshell.Wayland import qs.Common import qs.Services -import "../Common/ConnectorGeometry.js" as ConnectorGeometry Item { id: root @@ -18,6 +17,7 @@ Item { property Component overlayContent: null property alias overlayLoader: overlayLoader readonly property alias backgroundWindow: contentWindow + readonly property alias contentWindow: contentWindow property real popupWidth: 400 property real popupHeight: 300 property real triggerX: 0 @@ -38,8 +38,6 @@ Item { property bool fullHeightSurface: false property bool _primeContent: false property bool _resizeActive: false - property string _chromeClaimId: "" - property int _connectedChromeSerial: 0 property real _chromeAnimTravelX: 1 property real _chromeAnimTravelY: 1 property bool _fullSyncQueued: false @@ -55,7 +53,6 @@ Item { "rightBar": 0 }) property var screen: null - // Connected resize uses one full-screen surface; body-sized regions are masks. readonly property bool useBackgroundWindow: false readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, { "allow": ["top", "overlay"], @@ -93,13 +90,49 @@ Item { signal popoutClosed signal backgroundClicked - // Coalesce per-channel dirty bits; one ConnectedModeState write per tick. Timer { id: _syncTimer interval: 0 onTriggered: root._flushSync() } + ConnectedSurfaceLease { + id: chromeLease + claimPrefix: root.layerNamespace + screenName: root.screen ? root.screen.name : "" + enabled: root.frameOwnsConnectedChrome + active: contentWindow.visible || root.shouldBeVisible + presented: contentWindow.visible || root.shouldBeVisible + renewTokenOnRecovery: false + isCurrentOwner: function(name) { + return PopoutManager.isCurrentPopout(root.popoutHandle, name); + } + hasOwner: function(_name, ownerId) { + return ConnectedModeState.hasPopoutOwner(ownerId); + } + statePresent: function(name, ownerId) { + return ConnectedModeState.hasPopoutOwner(ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, "popout", ownerId); + } + claimState: function(_name, state, ownerId) { + return ConnectedModeState.claimPopout(ownerId, state); + } + ensureState: function(_name, state, ownerId) { + if (!ConnectedModeState.hasPopoutOwner(ownerId)) + return false; + return ConnectedModeState.updatePopout(ownerId, state); + } + releaseState: function(_name, ownerId) { + return ConnectedModeState.releasePopout(ownerId); + } + updateAnimationState: function(_name, ownerId, animX, animY) { + return ConnectedModeState.setPopoutAnim(ownerId, animX, animY); + } + updateBodyState: function(_name, ownerId, bodyX, bodyY, bodyW, bodyH) { + return ConnectedModeState.setPopoutBody(ownerId, bodyX, bodyY, bodyW, bodyH); + } + onRecoveryRequested: root._queueFullSync() + } + property var _lastOpenedScreen: null property bool isClosing: false @@ -169,11 +202,6 @@ Item { setBarContext(pos, bottomGap); } - function _nextChromeClaimId() { - _connectedChromeSerial += 1; - return layerNamespace + ":" + _connectedChromeSerial + ":" + (new Date()).getTime(); - } - function _captureChromeAnimTravel() { _chromeAnimTravelX = Math.max(1, Math.abs(contentContainer.offsetX)); _chromeAnimTravelY = Math.max(1, Math.abs(contentContainer.offsetY)); @@ -203,15 +231,35 @@ Item { function _connectedChromeState(visibleOverride) { const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible; + const presented = contentWindow.visible || root.shouldBeVisible; + const phase = root.isClosing ? "closing" : (!presented ? "hidden" : (!contentWindow.visible && root.shouldBeVisible ? "opening" : "open")); + const bodyRect = { + "x": root.alignedX, + "y": root.renderedAlignedY, + "width": root.alignedWidth, + "height": root.renderedAlignedHeight + }; + const animationOffset = { + "x": _connectedChromeAnimX(), + "y": _connectedChromeAnimY() + }; return { + "kind": "popout", + "screenName": root.screen ? root.screen.name : "", + "phase": phase, "visible": visible, + "presented": presented, "barSide": contentContainer.connectedBarSide, + "bodyRect": bodyRect, + "animationOffset": animationOffset, + "scale": 1, + "opacity": Theme.connectedSurfaceColor.a, "bodyX": root.alignedX, "bodyY": root.renderedAlignedY, "bodyW": root.alignedWidth, "bodyH": root.renderedAlignedHeight, - "animX": _connectedChromeAnimX(), - "animY": _connectedChromeAnimY(), + "animX": animationOffset.x, + "animY": animationOffset.y, "screen": root.screen ? root.screen.name : "", "omitStartConnector": root._closeGapOmitStartConnector(), "omitEndConnector": root._closeGapOmitEndConnector() @@ -219,34 +267,18 @@ Item { } function _publishConnectedChromeState(forceClaim, visibleOverride) { - if (!root.frameOwnsConnectedChrome || !root.screen || !_chromeClaimId) + if (!root.frameOwnsConnectedChrome || !root.screen) return false; - - const screenName = root.screen.name; - const isCurrent = PopoutManager.isCurrentPopout(popoutHandle, screenName); - if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) { - if (!isCurrent) - return false; - forceClaim = true; - } else if (forceClaim && !isCurrent) { - return false; - } - - const state = _connectedChromeState(visibleOverride); - return forceClaim ? ConnectedModeState.claimPopout(_chromeClaimId, state) : ConnectedModeState.updatePopout(_chromeClaimId, state); + return chromeLease.publish(_connectedChromeState(visibleOverride), !!forceClaim); } function _releaseConnectedChromeState() { - if (_chromeClaimId) - ConnectedModeState.releasePopout(_chromeClaimId); - _chromeClaimId = ""; + chromeLease.release(); } - // ─── Exposed animation state for ConnectedModeState ──────────────────── readonly property real contentAnimX: contentContainer.animX readonly property real contentAnimY: contentContainer.animY - // ─── ConnectedModeState sync ──────────────────────────────────────────── function _syncPopoutChromeState() { if (!root.frameOwnsConnectedChrome) { _releaseConnectedChromeState(); @@ -258,16 +290,11 @@ Item { } if (!contentWindow.visible && !shouldBeVisible) return; - if (!_chromeClaimId) { - if (!PopoutManager.isCurrentPopout(popoutHandle, root.screen.name)) - return; - _chromeClaimId = _nextChromeClaimId(); - } - _publishConnectedChromeState(!ConnectedModeState.hasPopoutOwner(_chromeClaimId)); + _publishConnectedChromeState(false); } function _syncPopoutAnim(axis) { - if (!root.frameOwnsConnectedChrome || !_chromeClaimId) + if (!root.frameOwnsConnectedChrome || !chromeLease.claimId) return; if (!contentWindow.visible && !shouldBeVisible) return; @@ -276,25 +303,15 @@ Item { const syncY = axis === "y" && (barSide === "top" || barSide === "bottom"); if (!syncX && !syncY) return; - if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) { - if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name)) - _queueFullSync(); - return; - } - ConnectedModeState.setPopoutAnim(_chromeClaimId, syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined); + chromeLease.updateAnim(syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined); } function _syncPopoutBody() { - if (!root.frameOwnsConnectedChrome || !_chromeClaimId) + if (!root.frameOwnsConnectedChrome || !chromeLease.claimId) return; if (!contentWindow.visible && !shouldBeVisible) return; - if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) { - if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name)) - _queueFullSync(); - return; - } - ConnectedModeState.setPopoutBody(_chromeClaimId, root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight); + chromeLease.updateBody(root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight); } property bool _animSyncQueued: false @@ -345,13 +362,10 @@ Item { Connections { target: contentWindow function onVisibleChanged() { - if (contentWindow.visible) { - if (!root._chromeClaimId) - root._chromeClaimId = root._nextChromeClaimId(); + if (contentWindow.visible) root._publishConnectedChromeState(true); - } else { + else root._releaseConnectedChromeState(); - } } } @@ -359,11 +373,8 @@ Item { target: SettingsData function onConnectedFrameModeActiveChanged() { if (root.frameOwnsConnectedChrome) { - if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) { - if (!root._chromeClaimId) - root._chromeClaimId = root._nextChromeClaimId(); + if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) root._publishConnectedChromeState(true); - } } else { root._releaseConnectedChromeState(); } @@ -376,16 +387,17 @@ Item { Connections { target: ConnectedModeState function onPopoutOwnerIdChanged() { - if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name) && !ConnectedModeState.hasPopoutOwner(root._chromeClaimId)) - root._queueFullSync(); + chromeLease.checkOwnershipRecovery(); + } + function onSurfaceDescriptorsChanged() { + chromeLease.checkStateRecovery(); } } Connections { target: PopoutManager function onPopoutChanged() { - if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) - root._queueFullSync(); + chromeLease.requestRecovery(); } } @@ -406,34 +418,26 @@ Item { const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen; if (screenChanged) { - // Hide on this tick so Qt actually tears down the wl_surface; the show - // gets deferred below so the unmap is processed before the remap. contentWindow.visible = false; } _lastOpenedScreen = screen; PopoutManager.showPopout(popoutHandle); if (contentContainer) { - // Snap morph closed only on a fresh open; on screen-change re-open we stay at 1 - // because shouldBeVisible doesn't change and won't drive morph back to 1. if (!shouldBeVisible) morph.openProgress = 0; _captureChromeAnimTravel(); } if (root.frameOwnsConnectedChrome) { - _chromeClaimId = _nextChromeClaimId(); + chromeLease.beginClaim(); _publishConnectedChromeState(true, true); } else { - _chromeClaimId = ""; + chromeLease.release(); } if (screenChanged) { - // Defer the show one event-loop tick. Qt coalesces a synchronous - // false→true visibility flip into a no-op, leaving WindowBlur committed - // to the previous screen's wl_surface. Splitting the flip across ticks - // forces a real surface destroy+create so BackgroundEffect.surfaceCreated - // fires and the blur region republishes on the new surface. + // Unmap/remap wl_surface across ticks so blur republishes on the new screen. Qt.callLater(() => { if (!root.shouldBeVisible) return; @@ -552,6 +556,18 @@ Item { return Math.abs(value - bound) <= Math.max(1, Theme.hairline(root.dpr) * 2); } + // Snap positions within connector radius flush to the frame edge (avoids pinched arcs). + function _snapNearFrameBound(value, minBound, maxBound, minIsFrame, maxIsFrame) { + if (!root.usesConnectedSurfaceChrome || !root.closeFrameGapsActive) + return value; + const snapDist = Theme.connectedCornerRadius; + if (maxIsFrame && value < maxBound && maxBound - value < snapDist && maxBound - value <= value - minBound) + return maxBound; + if (minIsFrame && value > minBound && value - minBound < snapDist) + return minBound; + return value; + } + function _closeGapClampedToFrameSide(side) { if (!root.closeFrameGapsActive) return false; @@ -607,7 +623,6 @@ Item { property real renderedAlignedY: alignedY property real renderedAlignedHeight: alignedHeight readonly property bool renderedGeometryGrowing: alignedHeight >= renderedAlignedHeight - // Snap rendered geometry while the entrance morph runs so it doesn't ride a second animation (side-bar ramp). readonly property bool _settlingToOpen: fullHeightSurface && shouldBeVisible && morphAnim.running Behavior on renderedAlignedY { @@ -657,8 +672,6 @@ Item { return 0; if (!root.usesConnectedSurfaceChrome) return exclusion; - // In a shared frame corner, the adjacent connected bar already occupies - // one rounded-corner radius before the popout's own connector begins. return exclusion + Theme.connectedCornerRadius * 2; } @@ -691,16 +704,16 @@ Item { switch (effectiveBarPosition) { case SettingsData.Position.Left: - // bar on left: left side is bar-adjacent (popupGap), right side is frame-perpendicular (edgeGap) return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGapRight, anchorX)); case SettingsData.Position.Right: - // bar on right: right side is bar-adjacent (popupGap), left side is frame-perpendicular (edgeGap) return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth)); default: const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2); - const minX = Math.max(edgeGapLeft, adjacentBarClearance(adjacentBarInfo.leftBar)); - const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, adjacentBarClearance(adjacentBarInfo.rightBar)); - return Math.max(minX, Math.min(maxX, rawX)); + const clearLeft = adjacentBarClearance(adjacentBarInfo.leftBar); + const clearRight = adjacentBarClearance(adjacentBarInfo.rightBar); + const minX = Math.max(edgeGapLeft, clearLeft); + const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, clearRight); + return _snapNearFrameBound(Math.max(minX, Math.min(maxX, rawX)), minX, maxX, edgeGapLeft >= clearLeft, edgeGapRight >= clearRight); } })(), dpr) @@ -712,44 +725,34 @@ Item { switch (effectiveBarPosition) { case SettingsData.Position.Bottom: - // bar on bottom: bottom side is bar-adjacent (popupGap), top side is frame-perpendicular (edgeGap) return Math.max(edgeGapTop, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight)); case SettingsData.Position.Top: - // bar on top: top side is bar-adjacent (popupGap), bottom side is frame-perpendicular (edgeGap) return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY)); default: const rawY = triggerY - (popupHeight / 2); - const minY = Math.max(edgeGapTop, adjacentBarClearance(adjacentBarInfo.topBar)); - const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, adjacentBarClearance(adjacentBarInfo.bottomBar)); - return Math.max(minY, Math.min(maxY, rawY)); + const clearTop = adjacentBarClearance(adjacentBarInfo.topBar); + const clearBottom = adjacentBarClearance(adjacentBarInfo.bottomBar); + const minY = Math.max(edgeGapTop, clearTop); + const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, clearBottom); + return _snapNearFrameBound(Math.max(minY, Math.min(maxY, rawY)), minY, maxY, edgeGapTop >= clearTop, edgeGapBottom >= clearBottom); } })(), dpr) - readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 - readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 - readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 - readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + readonly property real maskX: _dismissZone.x + readonly property real maskY: _dismissZone.y + readonly property real maskWidth: _dismissZone.width + readonly property real maskHeight: _dismissZone.height - readonly property real maskX: { - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); - } - - readonly property real maskY: { - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarTopExclusion, adjacentTopBar); - } - - readonly property real maskWidth: { - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar); - return Math.max(100, screenWidth - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar); - return Math.max(100, screenHeight - maskY - bottomExclusion); + DismissZone { + id: _dismissZone + barPosition: root.effectiveBarPosition + barX: root.barX + barY: root.barY + barWidth: root.barWidth + barHeight: root.barHeight + screenWidth: root.screenWidth + screenHeight: root.screenHeight + adjacentBarInfo: root.adjacentBarInfo } PanelWindow { @@ -764,10 +767,8 @@ 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 - // 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 @@ -781,17 +782,7 @@ Item { WlrLayershell.namespace: root.layerNamespace WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus) readonly property bool _fullHeight: root.fullHeightSurface anchors { @@ -813,7 +804,6 @@ Item { Region { id: contentInputMask - // Use bar-aware mask so bar widget clicks pass through when a popout is open. item: (shouldBeVisible && backgroundInteractive) ? backgroundDismissalMask : contentMaskRect } @@ -924,7 +914,6 @@ Item { readonly property real computedScaleCollapsed: root.animationScaleCollapsed - // openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1). QtObject { id: morph property real openProgress: 0 @@ -971,7 +960,6 @@ Item { clip: shouldClip - // Bound the clipping strictly to the bar side, allowing massive overflow on the other 3 sides for shadows x: shouldClip ? (contentContainer.barLeft ? -connectedClipAllowance : -clipOversize) : 0 y: shouldClip ? (contentContainer.barTop ? -connectedClipAllowance : -clipOversize) : 0 @@ -1008,22 +996,13 @@ Item { ElevationShadow { id: shadowSource - readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0 - readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraTop: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 - readonly property real extraBottom: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 - readonly property real bodyX: extraLeft - readonly property real bodyY: extraTop - readonly property real bodyWidth: rollOutAdjuster.baseWidth - readonly property real bodyHeight: rollOutAdjuster.baseHeight - - width: rollOutAdjuster.baseWidth + extraLeft + extraRight - height: rollOutAdjuster.baseHeight + extraTop + extraBottom + visible: !root.usesConnectedSurfaceChrome + width: rollOutAdjuster.baseWidth + height: rollOutAdjuster.baseHeight opacity: contentWrapper.publishedOpacity scale: contentWrapper.scale - x: contentWrapper.x - extraLeft - y: contentWrapper.y - extraTop + x: contentWrapper.x + y: contentWrapper.y level: root.shadowLevel direction: root.effectiveShadowDirection fallbackOffset: root.shadowFallbackOffset @@ -1035,49 +1014,49 @@ Item { targetColor: contentContainer.surfaceColor borderColor: contentContainer.surfaceBorderColor borderWidth: contentContainer.surfaceBorderWidth - useCustomSource: root.usesConnectedSurfaceChrome shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome + } - Item { + Item { + id: localChrome + visible: root.usesLocalConnectedSurfaceChrome + + readonly property real extraLeft: (contentContainer.barTop || contentContainer.barBottom) ? Theme.connectedCornerRadius : 0 + readonly property real extraTop: (contentContainer.barLeft || contentContainer.barRight) ? Theme.connectedCornerRadius : 0 + + readonly property bool shadowsOn: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) + readonly property real shadowBlurPx: root.shadowLevel && root.shadowLevel.blurPx !== undefined ? root.shadowLevel.blurPx : 0 + readonly property real shadowSpreadPx: root.shadowLevel && root.shadowLevel.spreadPx !== undefined ? root.shadowLevel.spreadPx : 0 + readonly property real shadowOffsetX: Theme.elevationOffsetXFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset) + readonly property real shadowOffsetY: Theme.elevationOffsetYFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset) + readonly property color shadowTint: Theme.elevationShadowColor(root.shadowLevel) + readonly property var ambient: Theme.elevationAmbient(root.shadowLevel) + readonly property real pad: shadowsOn ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), ambient.blurPx + ambient.spreadPx) + 2) : 0 + + width: rollOutAdjuster.baseWidth + extraLeft * 2 + height: rollOutAdjuster.baseHeight + extraTop * 2 + opacity: contentWrapper.publishedOpacity + scale: contentWrapper.scale + x: contentWrapper.x - extraLeft + y: contentWrapper.y - extraTop + + ShaderEffect { anchors.fill: parent - visible: root.usesLocalConnectedSurfaceChrome - clip: false + anchors.topMargin: contentContainer.barTop ? 0 : -localChrome.pad + anchors.bottomMargin: contentContainer.barBottom ? 0 : -localChrome.pad + anchors.leftMargin: contentContainer.barLeft ? 0 : -localChrome.pad + anchors.rightMargin: contentContainer.barRight ? 0 : -localChrome.pad + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/connected_chrome.frag.qsb") - Rectangle { - x: shadowSource.bodyX - y: shadowSource.bodyY - width: shadowSource.bodyWidth - height: shadowSource.bodyHeight - topLeftRadius: contentContainer.surfaceTopLeftRadius - topRightRadius: contentContainer.surfaceTopRightRadius - bottomLeftRadius: contentContainer.surfaceBottomLeftRadius - bottomRightRadius: contentContainer.surfaceBottomRightRadius - color: contentContainer.surfaceColor - } - - ConnectedCorner { - visible: root.usesConnectedSurfaceChrome - barSide: contentContainer.connectedBarSide - placement: "left" - spacing: 0 - connectorRadius: Theme.connectedCornerRadius - color: contentContainer.surfaceColor - dpr: root.dpr - x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr) - y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr) - } - - ConnectedCorner { - visible: root.usesConnectedSurfaceChrome - barSide: contentContainer.connectedBarSide - placement: "right" - spacing: 0 - connectorRadius: Theme.connectedCornerRadius - color: contentContainer.surfaceColor - dpr: root.dpr - x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr) - y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr) - } + property real widthPx: width + property real heightPx: height + property vector4d surfaceColor: Qt.vector4d(contentContainer.surfaceColor.r, contentContainer.surfaceColor.g, contentContainer.surfaceColor.b, contentContainer.surfaceColor.a) + property vector4d shadowColor: Qt.vector4d(localChrome.shadowTint.r, localChrome.shadowTint.g, localChrome.shadowTint.b, localChrome.shadowsOn ? localChrome.shadowTint.a : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, localChrome.shadowBlurPx), localChrome.shadowSpreadPx, localChrome.shadowOffsetX, localChrome.shadowOffsetY) + property vector4d ambientParam: Qt.vector4d(localChrome.ambient.blurPx, localChrome.ambient.spreadPx, localChrome.shadowsOn ? localChrome.ambient.alpha : 0, 0) + property vector4d bodyRect: Qt.vector4d((contentContainer.barLeft ? 0 : localChrome.pad) + localChrome.extraLeft, (contentContainer.barTop ? 0 : localChrome.pad) + localChrome.extraTop, rollOutAdjuster.baseWidth, rollOutAdjuster.baseHeight) + property vector4d cornerRadius: Qt.vector4d(contentContainer.surfaceTopLeftRadius, contentContainer.surfaceTopRightRadius, contentContainer.surfaceBottomRightRadius, contentContainer.surfaceBottomLeftRadius) + property vector4d edgeParam: Qt.vector4d(contentContainer.barTop ? 0 : (contentContainer.barBottom ? 1 : (contentContainer.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0) } } @@ -1135,8 +1114,6 @@ Item { Connections { target: contentWindow function onVisibleChanged() { - // open() flips contentWindow.visible to rebind the layer surface to - // a new screen; don't deactivate the wrapper while still open. if (!contentWindow.visible && !root.shouldBeVisible) contentWrapper._renderActive = false; } diff --git a/quickshell/Widgets/DankPopoutStandalone.qml b/quickshell/Widgets/DankPopoutStandalone.qml index 91ac2660..783388ae 100644 --- a/quickshell/Widgets/DankPopoutStandalone.qml +++ b/quickshell/Widgets/DankPopoutStandalone.qml @@ -18,6 +18,7 @@ Item { property Component overlayContent: null property alias overlayLoader: overlayLoader readonly property alias backgroundWindow: backgroundWindow + readonly property alias contentWindow: contentWindow property real popupWidth: 400 property real popupHeight: 300 property real triggerX: 0 @@ -494,31 +495,21 @@ Item { } })(), dpr) - readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 - readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 - readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 - readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + readonly property real maskX: _dismissZone.x + readonly property real maskY: _dismissZone.y + readonly property real maskWidth: _dismissZone.width + readonly property real maskHeight: _dismissZone.height - readonly property real maskX: { - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); - } - - readonly property real maskY: { - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarTopExclusion, adjacentTopBar); - } - - readonly property real maskWidth: { - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar); - return Math.max(100, screenWidth - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar); - return Math.max(100, screenHeight - maskY - bottomExclusion); + DismissZone { + id: _dismissZone + barPosition: root.effectiveBarPosition + barX: root.barX + barY: root.barY + barWidth: root.barWidth + barHeight: root.barHeight + screenWidth: root.screenWidth + screenHeight: root.screenHeight + adjacentBarInfo: root.adjacentBarInfo } PanelWindow { @@ -598,31 +589,25 @@ 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 WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus) anchors { left: true @@ -721,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/DismissZone.qml b/quickshell/Widgets/DismissZone.qml new file mode 100644 index 00000000..c463997f --- /dev/null +++ b/quickshell/Widgets/DismissZone.qml @@ -0,0 +1,25 @@ +import QtQuick +import qs.Common + +QtObject { + id: root + + property int barPosition: 0 + property real barX: 0 + property real barY: 0 + property real barWidth: 0 + property real barHeight: 0 + property real screenWidth: 0 + property real screenHeight: 0 + property var adjacentBarInfo: null + + readonly property real _leftExclusion: (barPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 + readonly property real _topExclusion: (barPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 + readonly property real _rightExclusion: (barPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 + readonly property real _bottomExclusion: (barPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + + readonly property real x: Math.max(_leftExclusion, adjacentBarInfo?.leftBar ?? 0) + readonly property real y: Math.max(_topExclusion, adjacentBarInfo?.topBar ?? 0) + readonly property real width: Math.max(100, screenWidth - x - Math.max(_rightExclusion, adjacentBarInfo?.rightBar ?? 0)) + readonly property real height: Math.max(100, screenHeight - y - Math.max(_bottomExclusion, adjacentBarInfo?.bottomBar ?? 0)) +} diff --git a/quickshell/Widgets/WindowBlur.qml b/quickshell/Widgets/WindowBlur.qml index aa8b8290..f4420dbf 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() { @@ -39,10 +52,7 @@ Item { targetWindow.BackgroundEffect.blurRegion = null; } - // Force BackgroundEffect to re-publish the blur region on the current wl_surface. - // Clearing first bypasses Quickshell's same-Region dedup in BackgroundEffect::setBlurRegion, - // setting pendingBlurRegion=true so the next polish actually ships the region — needed - // when the underlying surface has been remapped (e.g. PanelWindow.screen change). + // Re-publish blur region after wl_surface remaps (e.g. screen change). function kick() { if (!targetWindow) return;