From f8b32cc298e8b9ad1a10a1a2142e37d3c2b24be1 Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 9 Jun 2026 13:40:53 -0400 Subject: [PATCH] refactor(framemode): connected surfaces --- quickshell/Common/ConnectedModalChrome.qml | 130 ++-- quickshell/Common/ConnectedModeState.qml | 220 +++++- .../Common/ConnectedSurfaceDescriptor.js | 179 +++++ quickshell/Common/ConnectedSurfaceGeometry.js | 232 +++++++ quickshell/Common/ConnectedSurfaceLease.qml | 176 +++++ .../Modals/Common/DankModalConnected.qml | 30 +- .../DankLauncherV2ModalConnected.qml | 30 +- quickshell/Modules/Dock/Dock.qml | 33 +- quickshell/Modules/Frame/FrameBorder.qml | 65 +- quickshell/Modules/Frame/FrameWindow.qml | 631 +++++++++++------- .../Popup/NotificationPopupManager.qml | 21 +- quickshell/Shaders/frag/connected_arc.frag | 92 +++ quickshell/Shaders/qsb/connected_arc.frag.qsb | Bin 0 -> 5954 bytes quickshell/Widgets/DankPopoutConnected.qml | 138 ++-- 14 files changed, 1523 insertions(+), 454 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/Shaders/frag/connected_arc.frag create mode 100644 quickshell/Shaders/qsb/connected_arc.frag.qsb 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..e62e3e4a 100644 --- a/quickshell/Common/ConnectedModeState.qml +++ b/quickshell/Common/ConnectedModeState.qml @@ -3,10 +3,141 @@ 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 legacySurfaceState(screenName, kind) { + return SurfaceDescriptor.toLegacyState(surfaceDescriptor(screenName, kind)); + } + + function hasSurfaceDescriptor(screenName, kind, ownerId) { + const descriptor = surfaceDescriptor(screenName, kind); + return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId); + } + + 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; + } + + function _setSurfaceAnimation(screenName, kind, ownerId, x, y) { + const current = surfaceDescriptor(screenName, kind); + if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId)) + return false; + return true; + } + + function _setSurfaceBody(screenName, kind, ownerId, x, y, width, height) { + const current = surfaceDescriptor(screenName, kind); + if (current.phase === "hidden" || (ownerId && current.ownerId !== ownerId)) + return false; + return true; + } + readonly property var emptyDockState: ({ "reveal": false, "barSide": "bottom", @@ -69,8 +200,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 +236,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 +271,7 @@ Singleton { popoutScreen = ""; popoutOmitStartConnector = false; popoutOmitEndConnector = false; + _clearSurfaceDescriptor(releasedScreen, "popout", claimId); _bumpSurfaceRevision(releasedScreen); return true; } @@ -140,6 +289,7 @@ Singleton { if (!isNaN(nextY) && popoutAnimY !== nextY) popoutAnimY = nextY; } + _setSurfaceAnimation(popoutScreen, "popout", claimId, animX, animY); return true; } @@ -166,6 +316,7 @@ Singleton { if (!isNaN(nextH) && popoutBodyH !== nextH) popoutBodyH = nextH; } + _setSurfaceBody(popoutScreen, "popout", claimId, bodyX, bodyY, bodyW, bodyH); return true; } @@ -193,13 +344,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 legacyChanged = !_sameDockState(dockStates[screenName], normalized); + if (legacyChanged) { + 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,6 +371,7 @@ Singleton { const next = _cloneDict(dockStates); delete next[screenName]; dockStates = next; + _clearSurfaceDescriptor(screenName, "dock"); // Also clear corresponding slide if (dockSlides[screenName]) { @@ -237,6 +397,7 @@ Singleton { "y": numY }; dockSlides = next; + _setSurfaceAnimation(screenName, "dock", "dock:" + screenName, numX, numY); return true; } @@ -283,13 +444,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 legacyChanged = !_sameNotificationState(notificationStates[screenName], normalized); + if (legacyChanged) { + 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,6 +470,7 @@ Singleton { const next = _cloneDict(notificationStates); delete next[screenName]; notificationStates = next; + _clearSurfaceDescriptor(screenName, "notification"); _bumpSurfaceRevision(screenName); return true; } @@ -362,6 +531,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 +545,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; } @@ -418,6 +596,7 @@ Singleton { delete nextOwners[screenName]; modalOwners = nextOwners; } + _clearSurfaceDescriptor(screenName, "modal", ownerId); _bumpSurfaceRevision(screenName); return true; } @@ -438,6 +617,7 @@ Singleton { "animY": nay }); modalStates = next; + _setSurfaceAnimation(screenName, "modal", ownerId, animX, animY); return true; } @@ -461,6 +641,7 @@ Singleton { "bodyH": nh }); modalStates = next; + _setSurfaceBody(screenName, "modal", ownerId, bodyX, bodyY, bodyW, bodyH); return true; } @@ -543,6 +724,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..7a739789 --- /dev/null +++ b/quickshell/Common/ConnectedSurfaceDescriptor.js @@ -0,0 +1,179 @@ +.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; +} + +function toLegacyState(descriptor) { + var d = normalize(descriptor); + return { + "visible": d.visible, + "reveal": d.visible, + "barSide": d.barSide, + "bodyX": d.bodyRect.x, + "bodyY": d.bodyRect.y, + "bodyW": d.bodyRect.width, + "bodyH": d.bodyRect.height, + "animX": d.animationOffset.x, + "animY": d.animationOffset.y, + "slideX": d.animationOffset.x, + "slideY": d.animationOffset.y, + "screen": d.screenName, + "omitStartConnector": d.omitStartConnector, + "omitEndConnector": d.omitEndConnector + }; +} diff --git a/quickshell/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/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml index 30618bb1..5c1f372b 100644 --- a/quickshell/Modals/Common/DankModalConnected.qml +++ b/quickshell/Modals/Common/DankModalConnected.qml @@ -115,6 +115,7 @@ Item { id: modalChrome modalHandle: root.modalHandle claimPrefix: root.layerNamespace + ":modal" + surfaceKind: "modal" screenName: root._currentScreenName() enabled: root.frameOwnsConnectedChrome active: root.shouldBeVisible @@ -125,17 +126,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); } diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index fef28659..bc4cd745 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -242,6 +242,7 @@ Item { id: modalChrome modalHandle: root.modalHandle claimPrefix: "dms:launcher-v2" + surfaceKind: "launcher" screenName: root._currentScreenName() enabled: root.frameOwnsConnectedChrome active: root.spotlightOpen @@ -252,17 +253,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); } diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index 413d4dfb..4385a887 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 }); diff --git a/quickshell/Modules/Frame/FrameBorder.qml b/quickshell/Modules/Frame/FrameBorder.qml index 31112382..a59eefcf 100644 --- a/quickshell/Modules/Frame/FrameBorder.qml +++ b/quickshell/Modules/Frame/FrameBorder.qml @@ -1,9 +1,13 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects +import QtQuick.Shapes import qs.Common +// Frame perimeter ring: the full window rectangle with a rounded-rectangle +// cutout. Drawn as a single even-odd Shape so the ring is one primitive: no +// full-output mask textures, and a translucent fill never double-blends at the +// corners. Item { id: root @@ -16,39 +20,42 @@ Item { required property real cutoutRadius property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) - Rectangle { - id: borderRect + // Path elements can't reference `parent`; expose cutout edges as root props. + readonly property real _left: cutoutLeftInset + readonly property real _top: cutoutTopInset + readonly property real _right: width - cutoutRightInset + readonly property real _bottom: height - cutoutBottomInset + readonly property real _radius: Math.max(0, Math.min(cutoutRadius, (_right - _left) / 2, (_bottom - _top) / 2)) + Shape { anchors.fill: parent - // Bake frameOpacity into the color alpha rather than using the `opacity` property - color: root.borderColor + asynchronous: false + preferredRendererType: Shape.CurveRenderer + antialiasing: true - layer.enabled: true - layer.effect: MultiEffect { - maskSource: cutoutMask - maskEnabled: true - maskInverted: true - maskThresholdMin: 0.5 - maskSpreadAtMin: 1 - } - } + ShapePath { + fillColor: root.borderColor + strokeWidth: -1 + fillRule: ShapePath.OddEvenFill - Item { - id: cutoutMask + // Outer rectangle (window edge, square corners) + startX: 0 + startY: 0 + PathLine { x: root.width; y: 0 } + PathLine { x: root.width; y: root.height } + PathLine { x: 0; y: root.height } + PathLine { x: 0; y: 0 } - 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 + // Inner rounded-rectangle cutout (second subpath → even-odd hole) + PathMove { x: root._left + root._radius; y: root._top } + PathLine { x: root._right - root._radius; y: root._top } + PathArc { x: root._right; y: root._top + root._radius; radiusX: root._radius; radiusY: root._radius } + PathLine { x: root._right; y: root._bottom - root._radius } + PathArc { x: root._right - root._radius; y: root._bottom; radiusX: root._radius; radiusY: root._radius } + PathLine { x: root._left + root._radius; y: root._bottom } + PathArc { x: root._left; y: root._bottom - root._radius; radiusX: root._radius; radiusY: root._radius } + PathLine { x: root._left; y: root._top + root._radius } + PathArc { x: root._left + root._radius; y: root._top; radiusX: root._radius; radiusY: root._radius } } } } diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index c67c8d43..fcf8dd4d 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -8,6 +8,7 @@ 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 +47,14 @@ 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 var _popoutState: ConnectedModeState.legacySurfaceState(win._screenName, "popout") + readonly property var _dockState: ConnectedModeState.legacySurfaceState(win._screenName, "dock") + readonly property var _notifState: ConnectedModeState.legacySurfaceState(win._screenName, "notification") + readonly property var _modalState: ConnectedModeState.legacySurfaceState(win._screenName, "modal") readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen) readonly property string _barSide: { @@ -67,10 +69,15 @@ PanelWindow { } readonly property real _ccr: Theme.connectedCornerRadius - readonly property bool _popoutHorizontal: ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom" + readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.barSide) 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 var _popoutBodyGeometry: SurfaceGeometry.animatedBodyRect(win._popoutDescriptor, win._dpr) + readonly property var _popoutStaticBodyGeometry: SurfaceGeometry.bodyRect(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 @@ -91,7 +98,7 @@ PanelWindow { } 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 _popoutFillOverlapYValue: (win._popoutState.barSide === "left" || win._popoutState.barSide === "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 @@ -102,55 +109,109 @@ PanelWindow { // 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 _popoutChromeGeometry: SurfaceGeometry.chromeBounds(win._popoutStaticBodyGeometry, win._popoutDescriptor.barSide, win._effectivePopoutStartCcr, win._effectivePopoutEndCcr, 0, win._dpr) + 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 var _dockChromeGeometry: SurfaceGeometry.chromeBounds(win._dockBodyGeometry, win._dockDescriptor.barSide, win._dockConnectorRadiusValue, win._dockConnectorRadiusValue, 0, win._dpr) 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" + // The connected silhouette is drawn directly as one translucent Shape; the + // full-output layer/FBO is allocated only when it must source the elevation + // shadow. Translucency no longer needs a flatten (single primitive). + readonly property bool _elevationShadow: win._connectedActive && Theme.elevationEnabled && !win._disableLayer + // Active surfaces packed into four fixed SDF-shader slots. Each near (bar) + // edge is clamped to the cutout edge so the smooth-min connector attaches + // there; connR (the smin radius) is the connector fillet. + 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); + // smin radius = the near connector fillet only; an omitted + // connector contributes no flare (its corner just rounds). + const connR = Math.max(s.radii.startCr, s.radii.endCr); + const active = b.width > 0 && b.height > 0 ? 1 : 0; + // A bar-side corner is sharp only where its connector is present; + // an omitted connector (farStart/farEnd set) keeps that corner + // rounded. Far corners always round. (start=left/top, end=right/bottom.) + const bodyR = s.radii.surfaceRadius; + const omitS = s.radii.farStartCr > 0; + const omitE = s.radii.farEndCr > 0; + let tl = bodyR, tr = bodyR, br = bodyR, bl = bodyR; + if (s.side === "top") { + if (!omitS) tl = 0; + if (!omitE) tr = 0; + } else if (s.side === "bottom") { + if (!omitS) bl = 0; + if (!omitE) br = 0; + } else if (s.side === "left") { + if (!omitS) tl = 0; + if (!omitE) bl = 0; + } else { + if (!omitS) tr = 0; + if (!omitE) br = 0; + } + out.push({ + "rect": Qt.vector4d(b.x, b.y, b.width, b.height), + "corner": Qt.vector4d(tl, tr, br, bl), + "param": Qt.vector4d(connR, active, 0, 0) + }); + } else { + out.push({"rect": Qt.vector4d(0, 0, 0, 0), "corner": Qt.vector4d(0, 0, 0, 0), "param": Qt.vector4d(0, 0, 0, 0)}); + } + } + return out; + } property bool _surfaceRefreshNeedsLayerRecreate: false property bool _surfaceLayerRecoveryActive: false @@ -194,34 +255,33 @@ PanelWindow { id: _popoutBodyBlurAnchor visible: false - readonly property bool _active: ConnectedModeState.popoutVisible && ConnectedModeState.popoutScreen === win._screenName + readonly property bool _active: win._popoutDescriptor.visible + readonly property real _dyClamp: win._popoutBodyGeometry.dy + readonly property real _dxClamp: win._popoutBodyGeometry.dx - 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 + x: _active ? win._popoutBodyGeometry.x : 0 + y: _active ? win._popoutBodyGeometry.y : 0 + width: _active ? win._popoutBodyGeometry.width : 0 + height: _active ? win._popoutBodyGeometry.height : 0 } Item { id: _dockBodyBlurAnchor visible: false - readonly property bool _active: win._connectedActive && win._dockState.reveal && win._dockState.bodyW > 0 && win._dockState.bodyH > 0 + readonly property bool _active: win._connectedActive && win._dockDescriptor.visible && win._dockBodyGeometry.width > 0 && win._dockBodyGeometry.height > 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 + x: _active ? win._dockBodyGeometry.x : 0 + y: _active ? win._dockBodyGeometry.y : 0 + width: _active ? win._dockBodyGeometry.width : 0 + height: _active ? win._dockBodyGeometry.height : 0 } Item { id: _popoutBodyBlurCap opacity: 0 - readonly property string _side: ConnectedModeState.popoutBarSide + readonly property string _side: win._popoutState.barSide 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 @@ -254,13 +314,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "left", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -269,13 +328,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._popoutDescriptor.barSide, win._popoutBodyGeometry, "right", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -283,7 +341,7 @@ PanelWindow { opacity: 0 readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(ConnectedModeState.popoutBarSide, "left") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "left") readonly property real _radius: win._popoutConnectorRadiusLeft x: _active ? win._connectorCutoutX(_popoutLeftConnectorBlurAnchor.x, _popoutLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 @@ -297,7 +355,7 @@ PanelWindow { opacity: 0 readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 - readonly property string _arcCorner: ConnectorGeometry.arcCorner(ConnectedModeState.popoutBarSide, "right") + readonly property string _arcCorner: ConnectorGeometry.arcCorner(win._popoutState.barSide, "right") readonly property real _radius: win._popoutConnectorRadiusRight x: _active ? win._connectorCutoutX(_popoutRightConnectorBlurAnchor.x, _popoutRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 @@ -312,11 +370,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -325,11 +384,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -338,11 +398,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -351,11 +412,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -363,8 +425,8 @@ PanelWindow { 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 _barSide: win._farConnectorBarSide(win._popoutState.barSide, "left") + readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "left") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectivePopoutFarStartCcr @@ -379,8 +441,8 @@ PanelWindow { 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 _barSide: win._farConnectorBarSide(win._popoutState.barSide, "right") + readonly property string _placement: win._farConnectorPlacement(win._popoutState.barSide, "right") readonly property string _arcCorner: ConnectorGeometry.arcCorner(_barSide, _placement) readonly property real _radius: win._effectivePopoutFarEndCcr @@ -395,13 +457,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._dockDescriptor.barSide, win._dockBodyGeometry, "left", 0, win._dockConnectorRadiusValue, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -409,13 +470,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._dockDescriptor.barSide, win._dockBodyGeometry, "right", 0, win._dockConnectorRadiusValue, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -448,28 +508,26 @@ PanelWindow { id: _notifBodyBlurAnchor visible: false - readonly property bool _active: win._frameActive && win._notifState.visible && win._notifState.bodyW > 0 && win._notifState.bodyH > 0 + readonly property bool _active: win._frameActive && win._notifDescriptor.visible && win._notifBodyGeometry.width > 0 && win._notifBodyGeometry.height > 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 + x: _active ? win._notifBodyGeometry.x : 0 + y: _active ? win._notifBodyGeometry.y : 0 + width: _active ? win._notifBodyGeometry.width : 0 + height: _active ? win._notifBodyGeometry.height : 0 } Item { id: _modalBodyBlurAnchor visible: false - readonly property bool _active: win._frameActive && win._modalState.visible && win._modalState.bodyW > 0 && win._modalState.bodyH > 0 + readonly property bool _active: win._frameActive && win._modalDescriptor.visible && win._modalBodyGeometry.width > 0 && win._modalBodyGeometry.height > 0 + readonly property real _dyClamp: win._modalBodyGeometry.dy + readonly property real _dxClamp: win._modalBodyGeometry.dx - // 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 + x: _active ? win._modalBodyGeometry.x : 0 + y: _active ? win._modalBodyGeometry.y : 0 + width: _active ? win._modalBodyGeometry.width : 0 + height: _active ? win._modalBodyGeometry.height : 0 } Item { @@ -494,13 +552,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "left", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -509,13 +566,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._modalDescriptor.barSide, win._modalBodyGeometry, "right", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -552,11 +608,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -565,11 +622,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -578,11 +636,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -591,11 +650,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -665,13 +725,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "left", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -680,13 +739,12 @@ PanelWindow { 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) + readonly property var _rect: SurfaceGeometry.connectorRect(win._notifDescriptor.barSide, _notifBodySceneBlurAnchor, "right", 0, _radius, win._dpr) - 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -723,11 +781,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -736,11 +795,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -749,11 +809,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -762,11 +823,12 @@ PanelWindow { 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 ? 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 + x: _active ? _rect.x : 0 + y: _active ? _rect.y : 0 + width: _active ? _rect.width : 0 + height: _active ? _rect.height : 0 } Item { @@ -1017,23 +1079,19 @@ PanelWindow { } function _popoutChromeX() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyX - ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr : 0); + return win._popoutChromeGeometry.x; } function _popoutChromeY() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyY - ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr : 0); + return win._popoutChromeGeometry.y; } function _popoutChromeWidth() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyW + ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); + return win._popoutChromeGeometry.width; } function _popoutChromeHeight() { - const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyH + ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); + return win._popoutChromeGeometry.height; } function _popoutClipX() { @@ -1053,67 +1111,123 @@ PanelWindow { } function _popoutShapeBodyOffsetX() { - const side = ConnectedModeState.popoutBarSide; + const side = win._popoutState.barSide; if (ConnectorGeometry.isHorizontal(side)) return win._effectivePopoutStartCcr; return side === "right" ? win._effectivePopoutFarExtent : 0; } function _popoutShapeBodyOffsetY() { - const side = ConnectedModeState.popoutBarSide; + const side = win._popoutState.barSide; if (ConnectorGeometry.isHorizontal(side)) return side === "bottom" ? win._effectivePopoutFarExtent : 0; return win._effectivePopoutStartCcr; } function _popoutShapeWidth() { - const side = ConnectedModeState.popoutBarSide; + const side = win._popoutState.barSide; if (ConnectorGeometry.isHorizontal(side)) return win._popoutClipWidth() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; return win._popoutClipWidth() + win._effectivePopoutFarExtent; } function _popoutShapeHeight() { - const side = ConnectedModeState.popoutBarSide; + const side = win._popoutState.barSide; 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; + return (win._popoutState.barSide === "left" ? _popoutBodyBlurAnchor._dxClamp : 0) - win._popoutFillOverlapXValue; } function _popoutBodyYInClip() { - return (ConnectedModeState.popoutBarSide === "top" ? _popoutBodyBlurAnchor._dyClamp : 0) - win._popoutFillOverlapYValue; + return (win._popoutState.barSide === "top" ? _popoutBodyBlurAnchor._dyClamp : 0) - win._popoutFillOverlapYValue; } function _popoutBodyFullWidth() { - return ConnectedModeState.popoutBodyW + win._popoutFillOverlapXValue * 2; + return win._popoutState.bodyW + win._popoutFillOverlapXValue * 2; } function _popoutBodyFullHeight() { - return ConnectedModeState.popoutBodyH + win._popoutFillOverlapYValue * 2; + return win._popoutState.bodyH + win._popoutFillOverlapYValue * 2; + } + + // Active connected surfaces fed to the unified silhouette path. Raw animated + // body rects (no seam/fill overlap); the builder anchors each to the cutout + // edge. Connector radii match what each ConnectedShape would receive. + function _unifiedSurfaces() { + const arr = []; + const p = win._popoutBodyGeometry; + if (win._popoutDescriptor.visible && win._popoutState.screen === 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": { + "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": { + "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": { + "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": { + "startCr": win._dockConnectorRadiusValue, + "endCr": win._dockConnectorRadiusValue, + "farStartCr": 0, + "farEndCr": 0, + "surfaceRadius": win._dockBodyBlurRadiusValue + } + }); + return arr; } function _dockChromeX() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.x - ((dockSide === "top" || dockSide === "bottom") ? win._dockConnectorRadiusValue : 0); + return win._dockChromeGeometry.x; } function _dockChromeY() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.y - ((dockSide === "left" || dockSide === "right") ? win._dockConnectorRadiusValue : 0); + return win._dockChromeGeometry.y; } function _dockChromeWidth() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.width + ((dockSide === "top" || dockSide === "bottom") ? win._dockConnectorRadiusValue * 2 : 0); + return win._dockChromeGeometry.width; } function _dockChromeHeight() { - const dockSide = win._dockState.barSide; - return _dockBodyBlurAnchor.height + ((dockSide === "left" || dockSide === "right") ? win._dockConnectorRadiusValue * 2 : 0); + return win._dockChromeGeometry.height; } function _dockBodyXInChrome() { @@ -1148,38 +1262,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; @@ -1246,7 +1328,7 @@ PanelWindow { return; if (_surfaceRefreshNeedsLayerRecreate) { _surfaceRefreshNeedsLayerRecreate = false; - if (win._connectedActive && !win._disableLayer && (Theme.elevationEnabled || win._surfaceOpacity < 1)) { + if (win._elevationShadow) { _surfaceLayerRecoveryActive = true; surfaceLayerRestoreAction.restart(); } @@ -1332,7 +1414,7 @@ PanelWindow { surfaceRefreshAction.cancel(); surfaceLayerRestoreAction.cancel(); _surfaceRefreshNeedsLayerRecreate = true; - if (win._connectedActive && !win._disableLayer && (Theme.elevationEnabled || win._surfaceOpacity < 1)) + if (win._elevationShadow) _surfaceLayerRecoveryActive = true; win._teardownBlur(); } @@ -1367,9 +1449,12 @@ PanelWindow { id: _connectedSurfaceLayer 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) + // Elevation-off draws the silhouette directly at the surface color, so + // group opacity is 1. Elevation-on draws children opaque and applies the + // surface alpha to the whole group beneath the shadow. + opacity: win._elevationShadow ? win._surfaceOpacity : 1 + // FBO is allocated only to source the elevation shadow. + layer.enabled: win._elevationShadow && !win._surfaceLayerRecoveryActive layer.smooth: false layer.effect: MultiEffect { @@ -1390,8 +1475,38 @@ PanelWindow { shadowOpacity: 1 } + // Elevation-off: the entire connected silhouette (frame ring + every + // active chrome) as one SDF in a fragment shader. Analytic fwidth AA → + // crisp at any scale, no FBO; the smooth-min radius is the connector. + ShaderEffect { + anchors.fill: parent + visible: win._connectedActive && !win._elevationShadow + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_arc.frag.qsb") + + 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 chromeRect0: win._sdfSlots[0].rect + property vector4d chromeCorner0: win._sdfSlots[0].corner + property vector4d chromeParam0: win._sdfSlots[0].param + property vector4d chromeRect1: win._sdfSlots[1].rect + property vector4d chromeCorner1: win._sdfSlots[1].corner + property vector4d chromeParam1: win._sdfSlots[1].param + property vector4d chromeRect2: win._sdfSlots[2].rect + property vector4d chromeCorner2: win._sdfSlots[2].corner + property vector4d chromeParam2: win._sdfSlots[2].param + property vector4d chromeRect3: win._sdfSlots[3].rect + property vector4d chromeCorner3: win._sdfSlots[3].corner + property vector4d chromeParam3: win._sdfSlots[3].param + } + + // Elevation-on: opaque children flattened in the FBO so the MultiEffect + // can derive one shadow; the layer applies the surface alpha. FrameBorder { anchors.fill: parent + visible: win._elevationShadow borderColor: win._opaqueSurfaceColor cutoutTopInset: win.cutoutTopInset cutoutBottomInset: win.cutoutBottomInset @@ -1403,11 +1518,11 @@ PanelWindow { Item { id: _connectedChrome anchors.fill: parent - visible: win._connectedActive + visible: win._elevationShadow Item { id: _popoutChrome - visible: ConnectedModeState.popoutVisible && ConnectedModeState.popoutScreen === win._screenName + visible: win._popoutState.visible && win._popoutState.screen === win._screenName x: win._popoutChromeX() y: win._popoutChromeY() width: win._popoutChromeWidth() @@ -1424,7 +1539,7 @@ PanelWindow { ConnectedShape { id: _popoutShape visible: _popoutBodyBlurAnchor._active && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0 - barSide: ConnectedModeState.popoutBarSide + barSide: win._popoutState.barSide bodyWidth: win._popoutClipWidth() bodyHeight: win._popoutClipHeight() connectorRadius: win._effectivePopoutCcr @@ -1474,8 +1589,8 @@ PanelWindow { 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) + x: Theme.snap(_dockLeftConnectorBlurAnchor.x - _dockChrome.x, win._dpr) + y: Theme.snap(_dockLeftConnectorBlurAnchor.y - _dockChrome.y, win._dpr) } ConnectedCorner { @@ -1487,31 +1602,30 @@ PanelWindow { 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) + x: Theme.snap(_dockRightConnectorBlurAnchor.x - _dockChrome.x, win._dpr) + y: Theme.snap(_dockRightConnectorBlurAnchor.y - _dockChrome.y, win._dpr) } } } Item { id: _notifChrome - visible: _notifBodySceneBlurAnchor._active + visible: win._elevationShadow && _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) + readonly property var _geometry: SurfaceGeometry.chromeBounds(_notifBodySceneBlurAnchor, _notifSide, _startCcr, _endCcr, _farExtent, 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) + x: _geometry.x + y: _geometry.y + width: _geometry.width + height: _geometry.height ConnectedShape { visible: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 @@ -1534,7 +1648,7 @@ PanelWindow { // instead of sliding over bar widgets (mirrors the popout `_popoutClip`). Item { id: _modalClip - visible: _modalBodyBlurAnchor._active + visible: win._elevationShadow && _modalBodyBlurAnchor._active z: 1 readonly property string _modalSide: win._modalState.barSide @@ -1558,15 +1672,14 @@ PanelWindow { 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) + readonly property var _geometry: SurfaceGeometry.chromeBounds(_modalBodyBlurAnchor, _modalSide, _startCcr, _endCcr, _farExtent, 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) + x: Theme.snap(_geometry.x - _modalClip.x, win._dpr) + y: Theme.snap(_geometry.y - _modalClip.y, win._dpr) + width: _geometry.width + height: _geometry.height ConnectedShape { visible: _modalBodyBlurAnchor._active && _modalChrome._bodyW > 0 && _modalChrome._bodyH > 0 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/Shaders/frag/connected_arc.frag b/quickshell/Shaders/frag/connected_arc.frag new file mode 100644 index 00000000..c19a8d0a --- /dev/null +++ b/quickshell/Shaders/frag/connected_arc.frag @@ -0,0 +1,92 @@ +#version 450 + +// Connected Frame Mode silhouette as a signed-distance field: the frame ring +// (an inverted rounded rectangle) smooth-unioned with each active chrome +// (popout/modal, dock, notification). The smooth-min radius IS the connector +// fillet. Antialiasing is analytic via fwidth -> crisp at any scale, no FBO. + +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 + // Up to four chrome slots. rect = x,y,w,h (px). corner = per-corner radii + // (topLeft, topRight, bottomRight, bottomLeft). param = connectorR, active, 0, 0 + vec4 chromeRect0; + vec4 chromeCorner0; + vec4 chromeParam0; + vec4 chromeRect1; + vec4 chromeCorner1; + vec4 chromeParam1; + vec4 chromeRect2; + vec4 chromeCorner2; + vec4 chromeParam2; + vec4 chromeRect3; + vec4 chromeCorner3; + 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; +} + +// Per-corner rounded box. r = (topLeft, topRight, bottomRight, bottomLeft). +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; +} + +// Circular smooth-min: blends two SDFs with a fillet of radius k. +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); +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + + // Frame ring: inside the screen rect AND outside the rounded cutout (hole). + 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); + + // Smooth-union the active chrome surfaces; smin radius = connector fillet. + if (ubuf.chromeParam0.y > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect0, ubuf.chromeCorner0), ubuf.chromeParam0.x); + if (ubuf.chromeParam1.y > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect1, ubuf.chromeCorner1), ubuf.chromeParam1.x); + if (ubuf.chromeParam2.y > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect2, ubuf.chromeCorner2), ubuf.chromeParam2.x); + if (ubuf.chromeParam3.y > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect3, ubuf.chromeCorner3), ubuf.chromeParam3.x); + + 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..5e15a97df428dcc4b9d0939259d92b9e159b7a10 GIT binary patch literal 5954 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 k_#V>uCQ|=5yM;J#;J|?(kQtB*_P