1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-15 07:35:20 -04:00

refactor(framemode): unify connected surface chrome via SDF pipeline

- Shadow system rewrite with SDF quads
- Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders
- Improve frame blur performance
- Plugin performance gate
This commit is contained in:
purian23
2026-06-12 11:03:39 -04:00
parent 08fd6e26d8
commit d53809cf2b
52 changed files with 2588 additions and 2804 deletions
-151
View File
@@ -1,151 +0,0 @@
import QtQuick
import QtQuick.Shapes
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
// Concave arc connector filling the gap between a bar corner and an adjacent surface.
//
// NOTE: FrameWindow now uses ConnectedShape.qml for frame-owned connected chrome
// (unified single-path rendering). This component is still used by DankPopout's
// own shadow source for non-frame-owned chrome (popouts on non-frame screens).
Item {
id: root
property string barSide: "top"
property string placement: "left"
property real spacing: 4
property real connectorRadius: 12
property color color: "transparent"
property real edgeStrokeWidth: 0
property color edgeStrokeColor: color
property real dpr: 1
readonly property bool isHorizontalBar: barSide === "top" || barSide === "bottom"
readonly property bool isPlacementLeft: placement === "left"
readonly property real _edgeStrokeWidth: Math.max(0, edgeStrokeWidth)
readonly property string arcCorner: ConnectorGeometry.arcCorner(barSide, placement)
readonly property real pathStartX: {
switch (arcCorner) {
case "topLeft":
return width;
case "topRight":
case "bottomLeft":
return 0;
default:
return 0;
}
}
readonly property real pathStartY: {
switch (arcCorner) {
case "bottomRight":
return height;
default:
return 0;
}
}
readonly property real firstLineX: {
switch (arcCorner) {
case "topLeft":
case "bottomLeft":
return width;
default:
return 0;
}
}
readonly property real firstLineY: {
switch (arcCorner) {
case "topLeft":
case "topRight":
return height;
default:
return 0;
}
}
readonly property real secondLineX: {
switch (arcCorner) {
case "topRight":
case "bottomLeft":
case "bottomRight":
return width;
default:
return 0;
}
}
readonly property real secondLineY: {
switch (arcCorner) {
case "topLeft":
case "topRight":
case "bottomLeft":
return height;
default:
return 0;
}
}
readonly property real arcCenterX: arcCorner === "topRight" || arcCorner === "bottomRight" ? width : 0
readonly property real arcCenterY: arcCorner === "bottomLeft" || arcCorner === "bottomRight" ? height : 0
readonly property real arcStartAngle: {
switch (arcCorner) {
case "topLeft":
case "topRight":
return 90;
case "bottomLeft":
return 0;
default:
return -90;
}
}
readonly property real arcSweepAngle: {
switch (arcCorner) {
case "topRight":
return 90;
default:
return -90;
}
}
width: isHorizontalBar ? connectorRadius : (spacing + connectorRadius)
height: isHorizontalBar ? (spacing + connectorRadius) : connectorRadius
Shape {
x: -root._edgeStrokeWidth
y: -root._edgeStrokeWidth
width: root.width + root._edgeStrokeWidth * 2
height: root.height + root._edgeStrokeWidth * 2
asynchronous: false
antialiasing: true
preferredRendererType: Shape.CurveRenderer
layer.enabled: true
layer.smooth: true
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
ShapePath {
fillColor: root.color
strokeColor: root._edgeStrokeWidth > 0 ? root.edgeStrokeColor : "transparent"
strokeWidth: root._edgeStrokeWidth * 2
joinStyle: ShapePath.RoundJoin
capStyle: ShapePath.RoundCap
fillRule: ShapePath.WindingFill
startX: root.pathStartX + root._edgeStrokeWidth
startY: root.pathStartY + root._edgeStrokeWidth
PathLine {
x: root.firstLineX + root._edgeStrokeWidth
y: root.firstLineY + root._edgeStrokeWidth
}
PathLine {
x: root.secondLineX + root._edgeStrokeWidth
y: root.secondLineY + root._edgeStrokeWidth
}
PathAngleArc {
centerX: root.arcCenterX + root._edgeStrokeWidth
centerY: root.arcCenterY + root._edgeStrokeWidth
radiusX: root.connectorRadius
radiusY: root.connectorRadius
startAngle: root.arcStartAngle
sweepAngle: root.arcSweepAngle
}
}
}
}
-414
View File
@@ -1,414 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Common
// Unified connected silhouette: body + near/far concave arcs as one ShapePath.
// Keeping the connected chrome in one path avoids sibling alignment seams.
Item {
id: root
property string barSide: "top"
property real bodyWidth: 0
property real bodyHeight: 0
property real connectorRadius: 12
property real startConnectorRadius: connectorRadius
property real endConnectorRadius: connectorRadius
property real farStartConnectorRadius: 0
property real farEndConnectorRadius: 0
property real surfaceRadius: 12
property color fillColor: "transparent"
readonly property bool _horiz: barSide === "top" || barSide === "bottom"
readonly property real _sc: Math.max(0, startConnectorRadius)
readonly property real _ec: Math.max(0, endConnectorRadius)
readonly property real _fsc: Math.max(0, farStartConnectorRadius)
readonly property real _fec: Math.max(0, farEndConnectorRadius)
readonly property real _firstCr: barSide === "left" ? _sc : _ec
readonly property real _secondCr: barSide === "left" ? _ec : _sc
readonly property real _firstFarCr: barSide === "left" ? _fsc : _fec
readonly property real _secondFarCr: barSide === "left" ? _fec : _fsc
readonly property real _farExtent: Math.max(_fsc, _fec)
readonly property real _sr: Math.max(0, Math.min(surfaceRadius, (_horiz ? bodyWidth : bodyHeight) / 2, (_horiz ? bodyHeight : bodyWidth) / 2))
readonly property real _firstSr: _firstFarCr > 0 ? 0 : _sr
readonly property real _secondSr: _secondFarCr > 0 ? 0 : _sr
readonly property real _firstFarInset: _firstFarCr > 0 ? _firstFarCr : _firstSr
readonly property real _secondFarInset: _secondFarCr > 0 ? _secondFarCr : _secondSr
// Root-level aliases — PathArc/PathLine elements can't use `parent`.
readonly property real _bw: bodyWidth
readonly property real _bh: bodyHeight
readonly property real _bodyLeft: _horiz ? _sc : (barSide === "right" ? _farExtent : 0)
readonly property real _bodyRight: _bodyLeft + _bw
readonly property real _bodyTop: _horiz ? (barSide === "bottom" ? _farExtent : 0) : _sc
readonly property real _bodyBottom: _bodyTop + _bh
readonly property real _totalW: _horiz ? _bw + _sc + _ec : _bw + _farExtent
readonly property real _totalH: _horiz ? _bh + _farExtent : _bh + _sc + _ec
width: _totalW
height: _totalH
readonly property real bodyX: root._bodyLeft
readonly property real bodyY: root._bodyTop
Shape {
anchors.fill: parent
asynchronous: false
preferredRendererType: Shape.CurveRenderer
antialiasing: true
ShapePath {
fillColor: root.fillColor
strokeWidth: -1
fillRule: ShapePath.WindingFill
// CW path: bar edge → concave arc → body → convex arc → far edge → convex arc → body → concave arc
startX: root.barSide === "right" ? root._totalW : 0
startY: {
switch (root.barSide) {
case "bottom":
return root._totalH;
case "left":
return root._totalH;
case "right":
return 0;
default:
return 0;
}
}
// Bar edge
PathLine {
x: {
switch (root.barSide) {
case "left":
return 0;
case "right":
return root._totalW;
default:
return root._totalW;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._totalH;
case "left":
return 0;
case "right":
return root._totalH;
default:
return 0;
}
}
}
// Concave arc 1
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._firstCr;
case "right":
return -root._firstCr;
default:
return -root._firstCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._firstCr;
case "left":
return root._firstCr;
case "right":
return -root._firstCr;
default:
return root._firstCr;
}
}
radiusX: root._firstCr
radiusY: root._firstCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
// Body edge to first convex corner
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyRight - root._firstSr;
case "right":
return root._bodyLeft + root._firstSr;
default:
return root._bodyRight;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyTop + root._firstSr;
case "left":
return root._bodyTop;
case "right":
return root._bodyBottom;
default:
return root._bodyBottom - root._firstSr;
}
}
}
// Convex arc 1
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._firstSr;
case "right":
return -root._firstSr;
default:
return -root._firstSr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._firstSr;
case "left":
return root._firstSr;
case "right":
return -root._firstSr;
default:
return root._firstSr;
}
}
radiusX: root._firstSr
radiusY: root._firstSr
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
}
// Opposite-side connector 1
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._firstFarCr > 0 ? root._bodyRight + root._firstFarCr : root._bodyRight;
case "right":
return root._firstFarCr > 0 ? root._bodyLeft - root._firstFarCr : root._bodyLeft;
default:
return root._firstFarCr > 0 ? root._bodyRight : root._bodyRight - root._firstSr;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._firstFarCr > 0 ? root._bodyTop - root._firstFarCr : root._bodyTop;
case "left":
return root._firstFarCr > 0 ? root._bodyTop : root._bodyTop + root._firstSr;
case "right":
return root._firstFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._firstSr;
default:
return root._firstFarCr > 0 ? root._bodyBottom + root._firstFarCr : root._bodyBottom;
}
}
}
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._firstFarCr;
case "right":
return root._firstFarCr;
default:
return -root._firstFarCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._firstFarCr;
case "left":
return root._firstFarCr;
case "right":
return -root._firstFarCr;
default:
return -root._firstFarCr;
}
}
radiusX: root._firstFarCr
radiusY: root._firstFarCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
// Far edge
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyRight;
case "right":
return root._bodyLeft;
default:
return root._bodyLeft + root._secondFarInset;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyTop;
case "left":
return root._bodyBottom - root._secondFarInset;
case "right":
return root._bodyTop + root._secondFarInset;
default:
return root._bodyBottom;
}
}
}
// Opposite-side connector 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._secondFarCr;
case "right":
return -root._secondFarCr;
default:
return -root._secondFarCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._secondFarCr;
case "left":
return root._secondFarCr;
case "right":
return -root._secondFarCr;
default:
return root._secondFarCr;
}
}
radiusX: root._secondFarCr
radiusY: root._secondFarCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._secondFarCr > 0 ? root._bodyRight : root._bodyRight;
case "right":
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft;
default:
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft + root._secondSr;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop;
case "left":
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._secondSr;
case "right":
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop + root._secondSr;
default:
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom;
}
}
}
// Convex arc 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._secondSr;
case "right":
return root._secondSr;
default:
return -root._secondSr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._secondSr;
case "left":
return root._secondSr;
case "right":
return -root._secondSr;
default:
return -root._secondSr;
}
}
radiusX: root._secondSr
radiusY: root._secondSr
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
}
// Body edge to second concave arc
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyLeft + root._ec;
case "right":
return root._bodyRight - root._sc;
default:
return root._bodyLeft;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyBottom - root._sc;
case "left":
return root._bodyBottom;
case "right":
return root._bodyTop;
default:
return root._bodyTop + root._sc;
}
}
}
// Concave arc 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._secondCr;
case "right":
return root._secondCr;
default:
return -root._secondCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._secondCr;
case "left":
return root._secondCr;
case "right":
return -root._secondCr;
default:
return -root._secondCr;
}
}
radiusX: root._secondCr
radiusY: root._secondCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
}
}
}
-2
View File
@@ -289,8 +289,6 @@ PanelWindow {
borderColor: Theme.outlineMedium
borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
}
MouseArea {
+15 -2
View File
@@ -1,4 +1,5 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Services
@@ -50,6 +51,20 @@ Item {
readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader
readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader
readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
// Hyprland OnDemand grab: whitelist popout surfaces and bars so dismiss clicks still land.
HyprlandFocusGrab {
windows: {
const list = [];
if (root.contentWindow)
list.push(root.contentWindow);
if (root.backgroundWindow && root.backgroundWindow !== root.contentWindow)
list.push(root.backgroundWindow);
return list.concat(KeyboardFocus.barWindows);
}
active: KeyboardFocus.wantsGrab(root.shouldBeVisible, root.customKeyboardFocus)
}
Loader {
id: _fallbackContentLoader
@@ -127,8 +142,6 @@ Item {
return _usesConnectedBackendForScreen(targetScreen) ? connectedComp : standaloneComp;
}
// Defer Loader source-component swap until impl is fully closed; avoids
// tearing down a popout mid-animation when frame mode is toggled.
function _maybeResolveBackend() {
_resolveBackendForScreen(screen);
}
+161 -184
View File
@@ -5,7 +5,6 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
Item {
id: root
@@ -18,6 +17,7 @@ Item {
property Component overlayContent: null
property alias overlayLoader: overlayLoader
readonly property alias backgroundWindow: contentWindow
readonly property alias contentWindow: contentWindow
property real popupWidth: 400
property real popupHeight: 300
property real triggerX: 0
@@ -38,8 +38,6 @@ Item {
property bool fullHeightSurface: false
property bool _primeContent: false
property bool _resizeActive: false
property string _chromeClaimId: ""
property int _connectedChromeSerial: 0
property real _chromeAnimTravelX: 1
property real _chromeAnimTravelY: 1
property bool _fullSyncQueued: false
@@ -55,7 +53,6 @@ Item {
"rightBar": 0
})
property var screen: null
// Connected resize uses one full-screen surface; body-sized regions are masks.
readonly property bool useBackgroundWindow: false
readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
"allow": ["top", "overlay"],
@@ -93,13 +90,49 @@ Item {
signal popoutClosed
signal backgroundClicked
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer {
id: _syncTimer
interval: 0
onTriggered: root._flushSync()
}
ConnectedSurfaceLease {
id: chromeLease
claimPrefix: root.layerNamespace
screenName: root.screen ? root.screen.name : ""
enabled: root.frameOwnsConnectedChrome
active: contentWindow.visible || root.shouldBeVisible
presented: contentWindow.visible || root.shouldBeVisible
renewTokenOnRecovery: false
isCurrentOwner: function(name) {
return PopoutManager.isCurrentPopout(root.popoutHandle, name);
}
hasOwner: function(_name, ownerId) {
return ConnectedModeState.hasPopoutOwner(ownerId);
}
statePresent: function(name, ownerId) {
return ConnectedModeState.hasPopoutOwner(ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, "popout", ownerId);
}
claimState: function(_name, state, ownerId) {
return ConnectedModeState.claimPopout(ownerId, state);
}
ensureState: function(_name, state, ownerId) {
if (!ConnectedModeState.hasPopoutOwner(ownerId))
return false;
return ConnectedModeState.updatePopout(ownerId, state);
}
releaseState: function(_name, ownerId) {
return ConnectedModeState.releasePopout(ownerId);
}
updateAnimationState: function(_name, ownerId, animX, animY) {
return ConnectedModeState.setPopoutAnim(ownerId, animX, animY);
}
updateBodyState: function(_name, ownerId, bodyX, bodyY, bodyW, bodyH) {
return ConnectedModeState.setPopoutBody(ownerId, bodyX, bodyY, bodyW, bodyH);
}
onRecoveryRequested: root._queueFullSync()
}
property var _lastOpenedScreen: null
property bool isClosing: false
@@ -169,11 +202,6 @@ Item {
setBarContext(pos, bottomGap);
}
function _nextChromeClaimId() {
_connectedChromeSerial += 1;
return layerNamespace + ":" + _connectedChromeSerial + ":" + (new Date()).getTime();
}
function _captureChromeAnimTravel() {
_chromeAnimTravelX = Math.max(1, Math.abs(contentContainer.offsetX));
_chromeAnimTravelY = Math.max(1, Math.abs(contentContainer.offsetY));
@@ -203,15 +231,35 @@ Item {
function _connectedChromeState(visibleOverride) {
const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible;
const presented = contentWindow.visible || root.shouldBeVisible;
const phase = root.isClosing ? "closing" : (!presented ? "hidden" : (!contentWindow.visible && root.shouldBeVisible ? "opening" : "open"));
const bodyRect = {
"x": root.alignedX,
"y": root.renderedAlignedY,
"width": root.alignedWidth,
"height": root.renderedAlignedHeight
};
const animationOffset = {
"x": _connectedChromeAnimX(),
"y": _connectedChromeAnimY()
};
return {
"kind": "popout",
"screenName": root.screen ? root.screen.name : "",
"phase": phase,
"visible": visible,
"presented": presented,
"barSide": contentContainer.connectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": root.alignedX,
"bodyY": root.renderedAlignedY,
"bodyW": root.alignedWidth,
"bodyH": root.renderedAlignedHeight,
"animX": _connectedChromeAnimX(),
"animY": _connectedChromeAnimY(),
"animX": animationOffset.x,
"animY": animationOffset.y,
"screen": root.screen ? root.screen.name : "",
"omitStartConnector": root._closeGapOmitStartConnector(),
"omitEndConnector": root._closeGapOmitEndConnector()
@@ -219,34 +267,18 @@ Item {
}
function _publishConnectedChromeState(forceClaim, visibleOverride) {
if (!root.frameOwnsConnectedChrome || !root.screen || !_chromeClaimId)
if (!root.frameOwnsConnectedChrome || !root.screen)
return false;
const screenName = root.screen.name;
const isCurrent = PopoutManager.isCurrentPopout(popoutHandle, screenName);
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
if (!isCurrent)
return false;
forceClaim = true;
} else if (forceClaim && !isCurrent) {
return false;
}
const state = _connectedChromeState(visibleOverride);
return forceClaim ? ConnectedModeState.claimPopout(_chromeClaimId, state) : ConnectedModeState.updatePopout(_chromeClaimId, state);
return chromeLease.publish(_connectedChromeState(visibleOverride), !!forceClaim);
}
function _releaseConnectedChromeState() {
if (_chromeClaimId)
ConnectedModeState.releasePopout(_chromeClaimId);
_chromeClaimId = "";
chromeLease.release();
}
// ─── Exposed animation state for ConnectedModeState ────────────────────
readonly property real contentAnimX: contentContainer.animX
readonly property real contentAnimY: contentContainer.animY
// ─── ConnectedModeState sync ────────────────────────────────────────────
function _syncPopoutChromeState() {
if (!root.frameOwnsConnectedChrome) {
_releaseConnectedChromeState();
@@ -258,16 +290,11 @@ Item {
}
if (!contentWindow.visible && !shouldBeVisible)
return;
if (!_chromeClaimId) {
if (!PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
return;
_chromeClaimId = _nextChromeClaimId();
}
_publishConnectedChromeState(!ConnectedModeState.hasPopoutOwner(_chromeClaimId));
_publishConnectedChromeState(false);
}
function _syncPopoutAnim(axis) {
if (!root.frameOwnsConnectedChrome || !_chromeClaimId)
if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
return;
if (!contentWindow.visible && !shouldBeVisible)
return;
@@ -276,25 +303,15 @@ Item {
const syncY = axis === "y" && (barSide === "top" || barSide === "bottom");
if (!syncX && !syncY)
return;
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
_queueFullSync();
return;
}
ConnectedModeState.setPopoutAnim(_chromeClaimId, syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
chromeLease.updateAnim(syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
}
function _syncPopoutBody() {
if (!root.frameOwnsConnectedChrome || !_chromeClaimId)
if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
return;
if (!contentWindow.visible && !shouldBeVisible)
return;
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
_queueFullSync();
return;
}
ConnectedModeState.setPopoutBody(_chromeClaimId, root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
chromeLease.updateBody(root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
}
property bool _animSyncQueued: false
@@ -345,13 +362,10 @@ Item {
Connections {
target: contentWindow
function onVisibleChanged() {
if (contentWindow.visible) {
if (!root._chromeClaimId)
root._chromeClaimId = root._nextChromeClaimId();
if (contentWindow.visible)
root._publishConnectedChromeState(true);
} else {
else
root._releaseConnectedChromeState();
}
}
}
@@ -359,11 +373,8 @@ Item {
target: SettingsData
function onConnectedFrameModeActiveChanged() {
if (root.frameOwnsConnectedChrome) {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) {
if (!root._chromeClaimId)
root._chromeClaimId = root._nextChromeClaimId();
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name))
root._publishConnectedChromeState(true);
}
} else {
root._releaseConnectedChromeState();
}
@@ -376,16 +387,17 @@ Item {
Connections {
target: ConnectedModeState
function onPopoutOwnerIdChanged() {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name) && !ConnectedModeState.hasPopoutOwner(root._chromeClaimId))
root._queueFullSync();
chromeLease.checkOwnershipRecovery();
}
function onSurfaceDescriptorsChanged() {
chromeLease.checkStateRecovery();
}
}
Connections {
target: PopoutManager
function onPopoutChanged() {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name))
root._queueFullSync();
chromeLease.requestRecovery();
}
}
@@ -406,34 +418,26 @@ Item {
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false;
}
_lastOpenedScreen = screen;
PopoutManager.showPopout(popoutHandle);
if (contentContainer) {
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
if (!shouldBeVisible)
morph.openProgress = 0;
_captureChromeAnimTravel();
}
if (root.frameOwnsConnectedChrome) {
_chromeClaimId = _nextChromeClaimId();
chromeLease.beginClaim();
_publishConnectedChromeState(true, true);
} else {
_chromeClaimId = "";
chromeLease.release();
}
if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous
// false→true visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
// Unmap/remap wl_surface across ticks so blur republishes on the new screen.
Qt.callLater(() => {
if (!root.shouldBeVisible)
return;
@@ -552,6 +556,18 @@ Item {
return Math.abs(value - bound) <= Math.max(1, Theme.hairline(root.dpr) * 2);
}
// Snap positions within connector radius flush to the frame edge (avoids pinched arcs).
function _snapNearFrameBound(value, minBound, maxBound, minIsFrame, maxIsFrame) {
if (!root.usesConnectedSurfaceChrome || !root.closeFrameGapsActive)
return value;
const snapDist = Theme.connectedCornerRadius;
if (maxIsFrame && value < maxBound && maxBound - value < snapDist && maxBound - value <= value - minBound)
return maxBound;
if (minIsFrame && value > minBound && value - minBound < snapDist)
return minBound;
return value;
}
function _closeGapClampedToFrameSide(side) {
if (!root.closeFrameGapsActive)
return false;
@@ -607,7 +623,6 @@ Item {
property real renderedAlignedY: alignedY
property real renderedAlignedHeight: alignedHeight
readonly property bool renderedGeometryGrowing: alignedHeight >= renderedAlignedHeight
// Snap rendered geometry while the entrance morph runs so it doesn't ride a second animation (side-bar ramp).
readonly property bool _settlingToOpen: fullHeightSurface && shouldBeVisible && morphAnim.running
Behavior on renderedAlignedY {
@@ -657,8 +672,6 @@ Item {
return 0;
if (!root.usesConnectedSurfaceChrome)
return exclusion;
// In a shared frame corner, the adjacent connected bar already occupies
// one rounded-corner radius before the popout's own connector begins.
return exclusion + Theme.connectedCornerRadius * 2;
}
@@ -691,16 +704,16 @@ Item {
switch (effectiveBarPosition) {
case SettingsData.Position.Left:
// bar on left: left side is bar-adjacent (popupGap), right side is frame-perpendicular (edgeGap)
return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGapRight, anchorX));
case SettingsData.Position.Right:
// bar on right: right side is bar-adjacent (popupGap), left side is frame-perpendicular (edgeGap)
return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth));
default:
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
const minX = Math.max(edgeGapLeft, adjacentBarClearance(adjacentBarInfo.leftBar));
const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, adjacentBarClearance(adjacentBarInfo.rightBar));
return Math.max(minX, Math.min(maxX, rawX));
const clearLeft = adjacentBarClearance(adjacentBarInfo.leftBar);
const clearRight = adjacentBarClearance(adjacentBarInfo.rightBar);
const minX = Math.max(edgeGapLeft, clearLeft);
const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, clearRight);
return _snapNearFrameBound(Math.max(minX, Math.min(maxX, rawX)), minX, maxX, edgeGapLeft >= clearLeft, edgeGapRight >= clearRight);
}
})(), dpr)
@@ -712,44 +725,34 @@ Item {
switch (effectiveBarPosition) {
case SettingsData.Position.Bottom:
// bar on bottom: bottom side is bar-adjacent (popupGap), top side is frame-perpendicular (edgeGap)
return Math.max(edgeGapTop, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight));
case SettingsData.Position.Top:
// bar on top: top side is bar-adjacent (popupGap), bottom side is frame-perpendicular (edgeGap)
return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY));
default:
const rawY = triggerY - (popupHeight / 2);
const minY = Math.max(edgeGapTop, adjacentBarClearance(adjacentBarInfo.topBar));
const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, adjacentBarClearance(adjacentBarInfo.bottomBar));
return Math.max(minY, Math.min(maxY, rawY));
const clearTop = adjacentBarClearance(adjacentBarInfo.topBar);
const clearBottom = adjacentBarClearance(adjacentBarInfo.bottomBar);
const minY = Math.max(edgeGapTop, clearTop);
const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, clearBottom);
return _snapNearFrameBound(Math.max(minY, Math.min(maxY, rawY)), minY, maxY, edgeGapTop >= clearTop, edgeGapBottom >= clearBottom);
}
})(), dpr)
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
readonly property real maskX: _dismissZone.x
readonly property real maskY: _dismissZone.y
readonly property real maskWidth: _dismissZone.width
readonly property real maskHeight: _dismissZone.height
readonly property real maskX: {
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
}
readonly property real maskY: {
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
}
readonly property real maskWidth: {
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
return Math.max(100, screenWidth - maskX - rightExclusion);
}
readonly property real maskHeight: {
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
return Math.max(100, screenHeight - maskY - bottomExclusion);
DismissZone {
id: _dismissZone
barPosition: root.effectiveBarPosition
barX: root.barX
barY: root.barY
barWidth: root.barWidth
barHeight: root.barHeight
screenWidth: root.screenWidth
screenHeight: root.screenHeight
adjacentBarInfo: root.adjacentBarInfo
}
PanelWindow {
@@ -764,10 +767,8 @@ Item {
blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, contentContainer.scaleValue)
readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome || Theme.isDirectionalEffect
readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome
// Directional popouts clip to the bar edge, so the blur needs to grow from
// that same edge instead of translating through the bar before settling.
readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0
readonly property real _dxClamp: (contentContainer.barLeft || contentContainer.barRight) ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0
@@ -781,17 +782,7 @@ Item {
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: root.effectivePopoutLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
readonly property bool _fullHeight: root.fullHeightSurface
anchors {
@@ -813,7 +804,6 @@ Item {
Region {
id: contentInputMask
// Use bar-aware mask so bar widget clicks pass through when a popout is open.
item: (shouldBeVisible && backgroundInteractive) ? backgroundDismissalMask : contentMaskRect
}
@@ -924,7 +914,6 @@ Item {
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject {
id: morph
property real openProgress: 0
@@ -971,7 +960,6 @@ Item {
clip: shouldClip
// Bound the clipping strictly to the bar side, allowing massive overflow on the other 3 sides for shadows
x: shouldClip ? (contentContainer.barLeft ? -connectedClipAllowance : -clipOversize) : 0
y: shouldClip ? (contentContainer.barTop ? -connectedClipAllowance : -clipOversize) : 0
@@ -1008,22 +996,13 @@ Item {
ElevationShadow {
id: shadowSource
readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0
readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0
readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0
readonly property real extraTop: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0
readonly property real extraBottom: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0
readonly property real bodyX: extraLeft
readonly property real bodyY: extraTop
readonly property real bodyWidth: rollOutAdjuster.baseWidth
readonly property real bodyHeight: rollOutAdjuster.baseHeight
width: rollOutAdjuster.baseWidth + extraLeft + extraRight
height: rollOutAdjuster.baseHeight + extraTop + extraBottom
visible: !root.usesConnectedSurfaceChrome
width: rollOutAdjuster.baseWidth
height: rollOutAdjuster.baseHeight
opacity: contentWrapper.publishedOpacity
scale: contentWrapper.scale
x: contentWrapper.x - extraLeft
y: contentWrapper.y - extraTop
x: contentWrapper.x
y: contentWrapper.y
level: root.shadowLevel
direction: root.effectiveShadowDirection
fallbackOffset: root.shadowFallbackOffset
@@ -1035,49 +1014,49 @@ Item {
targetColor: contentContainer.surfaceColor
borderColor: contentContainer.surfaceBorderColor
borderWidth: contentContainer.surfaceBorderWidth
useCustomSource: root.usesConnectedSurfaceChrome
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome
}
Item {
Item {
id: localChrome
visible: root.usesLocalConnectedSurfaceChrome
readonly property real extraLeft: (contentContainer.barTop || contentContainer.barBottom) ? Theme.connectedCornerRadius : 0
readonly property real extraTop: (contentContainer.barLeft || contentContainer.barRight) ? Theme.connectedCornerRadius : 0
readonly property bool shadowsOn: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
readonly property real shadowBlurPx: root.shadowLevel && root.shadowLevel.blurPx !== undefined ? root.shadowLevel.blurPx : 0
readonly property real shadowSpreadPx: root.shadowLevel && root.shadowLevel.spreadPx !== undefined ? root.shadowLevel.spreadPx : 0
readonly property real shadowOffsetX: Theme.elevationOffsetXFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
readonly property real shadowOffsetY: Theme.elevationOffsetYFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
readonly property color shadowTint: Theme.elevationShadowColor(root.shadowLevel)
readonly property var ambient: Theme.elevationAmbient(root.shadowLevel)
readonly property real pad: shadowsOn ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), ambient.blurPx + ambient.spreadPx) + 2) : 0
width: rollOutAdjuster.baseWidth + extraLeft * 2
height: rollOutAdjuster.baseHeight + extraTop * 2
opacity: contentWrapper.publishedOpacity
scale: contentWrapper.scale
x: contentWrapper.x - extraLeft
y: contentWrapper.y - extraTop
ShaderEffect {
anchors.fill: parent
visible: root.usesLocalConnectedSurfaceChrome
clip: false
anchors.topMargin: contentContainer.barTop ? 0 : -localChrome.pad
anchors.bottomMargin: contentContainer.barBottom ? 0 : -localChrome.pad
anchors.leftMargin: contentContainer.barLeft ? 0 : -localChrome.pad
anchors.rightMargin: contentContainer.barRight ? 0 : -localChrome.pad
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/connected_chrome.frag.qsb")
Rectangle {
x: shadowSource.bodyX
y: shadowSource.bodyY
width: shadowSource.bodyWidth
height: shadowSource.bodyHeight
topLeftRadius: contentContainer.surfaceTopLeftRadius
topRightRadius: contentContainer.surfaceTopRightRadius
bottomLeftRadius: contentContainer.surfaceBottomLeftRadius
bottomRightRadius: contentContainer.surfaceBottomRightRadius
color: contentContainer.surfaceColor
}
ConnectedCorner {
visible: root.usesConnectedSurfaceChrome
barSide: contentContainer.connectedBarSide
placement: "left"
spacing: 0
connectorRadius: Theme.connectedCornerRadius
color: contentContainer.surfaceColor
dpr: root.dpr
x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr)
y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr)
}
ConnectedCorner {
visible: root.usesConnectedSurfaceChrome
barSide: contentContainer.connectedBarSide
placement: "right"
spacing: 0
connectorRadius: Theme.connectedCornerRadius
color: contentContainer.surfaceColor
dpr: root.dpr
x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr)
y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr)
}
property real widthPx: width
property real heightPx: height
property vector4d surfaceColor: Qt.vector4d(contentContainer.surfaceColor.r, contentContainer.surfaceColor.g, contentContainer.surfaceColor.b, contentContainer.surfaceColor.a)
property vector4d shadowColor: Qt.vector4d(localChrome.shadowTint.r, localChrome.shadowTint.g, localChrome.shadowTint.b, localChrome.shadowsOn ? localChrome.shadowTint.a : 0)
property vector4d shadowParam: Qt.vector4d(Math.max(0, localChrome.shadowBlurPx), localChrome.shadowSpreadPx, localChrome.shadowOffsetX, localChrome.shadowOffsetY)
property vector4d ambientParam: Qt.vector4d(localChrome.ambient.blurPx, localChrome.ambient.spreadPx, localChrome.shadowsOn ? localChrome.ambient.alpha : 0, 0)
property vector4d bodyRect: Qt.vector4d((contentContainer.barLeft ? 0 : localChrome.pad) + localChrome.extraLeft, (contentContainer.barTop ? 0 : localChrome.pad) + localChrome.extraTop, rollOutAdjuster.baseWidth, rollOutAdjuster.baseHeight)
property vector4d cornerRadius: Qt.vector4d(contentContainer.surfaceTopLeftRadius, contentContainer.surfaceTopRightRadius, contentContainer.surfaceBottomRightRadius, contentContainer.surfaceBottomLeftRadius)
property vector4d edgeParam: Qt.vector4d(contentContainer.barTop ? 0 : (contentContainer.barBottom ? 1 : (contentContainer.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
}
}
@@ -1135,8 +1114,6 @@ Item {
Connections {
target: contentWindow
function onVisibleChanged() {
// open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false;
}
+28 -41
View File
@@ -18,6 +18,7 @@ Item {
property Component overlayContent: null
property alias overlayLoader: overlayLoader
readonly property alias backgroundWindow: backgroundWindow
readonly property alias contentWindow: contentWindow
property real popupWidth: 400
property real popupHeight: 300
property real triggerX: 0
@@ -494,31 +495,21 @@ Item {
}
})(), dpr)
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
readonly property real maskX: _dismissZone.x
readonly property real maskY: _dismissZone.y
readonly property real maskWidth: _dismissZone.width
readonly property real maskHeight: _dismissZone.height
readonly property real maskX: {
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
}
readonly property real maskY: {
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
}
readonly property real maskWidth: {
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
return Math.max(100, screenWidth - maskX - rightExclusion);
}
readonly property real maskHeight: {
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
return Math.max(100, screenHeight - maskY - bottomExclusion);
DismissZone {
id: _dismissZone
barPosition: root.effectiveBarPosition
barX: root.barX
barY: root.barY
barWidth: root.barWidth
barHeight: root.barHeight
screenWidth: root.screenWidth
screenHeight: root.screenHeight
adjacentBarInfo: root.adjacentBarInfo
}
PanelWindow {
@@ -598,31 +589,25 @@ Item {
id: popoutBlur
targetWindow: contentWindow
readonly property real s: Math.min(1, contentContainer.scaleValue)
readonly property bool trackBlurFromBarEdge: root.fluidStandaloneActive
readonly property real op: Math.max(0, Math.min(1, (morph.openProgress - 0.08) * 1.6))
readonly property bool blurAlive: trackBlurFromBarEdge ? (contentContainer.revealWidth > 0 && contentContainer.revealHeight > 0) : root.shouldBeVisible
readonly property bool revealClipActive: root.fluidStandaloneActive
blurX: trackBlurFromBarEdge ? contentContainer.x + contentContainer.revealX : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: trackBlurFromBarEdge ? contentContainer.y + contentContainer.revealY : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealWidth : contentContainer.width * s * op) : 0
blurHeight: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealHeight : contentContainer.height * s * op) : 0
blurX: revealClipActive ? contentContainer.x : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: revealClipActive ? contentContainer.y : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: root.shouldBeVisible ? (revealClipActive ? contentContainer.width : contentContainer.width * s * op) : 0
blurHeight: root.shouldBeVisible ? (revealClipActive ? contentContainer.height : contentContainer.height * s * op) : 0
blurRadius: Theme.cornerRadius
clipEnabled: revealClipActive
clipX: contentContainer.x + contentContainer.revealX
clipY: contentContainer.y + contentContainer.revealY
clipWidth: root.shouldBeVisible ? contentContainer.revealWidth : 0
clipHeight: root.shouldBeVisible ? contentContainer.revealHeight : 0
}
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: root.effectivePopoutLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
anchors {
left: true
@@ -721,6 +706,8 @@ Item {
QtObject {
id: morph
property real openProgress: 0
onOpenProgressChanged: if (root.fluidStandaloneActive)
root._kickBlurCommit()
Behavior on openProgress {
enabled: root.animationsEnabled
NumberAnimation {
+25
View File
@@ -0,0 +1,25 @@
import QtQuick
import qs.Common
QtObject {
id: root
property int barPosition: 0
property real barX: 0
property real barY: 0
property real barWidth: 0
property real barHeight: 0
property real screenWidth: 0
property real screenHeight: 0
property var adjacentBarInfo: null
readonly property real _leftExclusion: (barPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
readonly property real _topExclusion: (barPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
readonly property real _rightExclusion: (barPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
readonly property real _bottomExclusion: (barPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
readonly property real x: Math.max(_leftExclusion, adjacentBarInfo?.leftBar ?? 0)
readonly property real y: Math.max(_topExclusion, adjacentBarInfo?.topBar ?? 0)
readonly property real width: Math.max(100, screenWidth - x - Math.max(_rightExclusion, adjacentBarInfo?.rightBar ?? 0))
readonly property real height: Math.max(100, screenHeight - y - Math.max(_bottomExclusion, adjacentBarInfo?.bottomBar ?? 0))
}
+14 -4
View File
@@ -16,6 +16,11 @@ Item {
property real blurWidth: 0
property real blurHeight: 0
property real blurRadius: 0
property bool clipEnabled: false
property real clipX: blurX
property real clipY: blurY
property real clipWidth: blurWidth
property real clipHeight: blurHeight
readonly property bool _active: blurEnabled && BlurService.enabled && !!targetWindow
@@ -26,6 +31,14 @@ Item {
width: root.blurWidth
height: root.blurHeight
radius: root.blurRadius
Region {
intersection: Intersection.Intersect
x: root.clipEnabled ? root.clipX : root.blurX
y: root.clipEnabled ? root.clipY : root.blurY
width: root.clipEnabled ? root.clipWidth : root.blurWidth
height: root.clipEnabled ? root.clipHeight : root.blurHeight
}
}
function _apply() {
@@ -39,10 +52,7 @@ Item {
targetWindow.BackgroundEffect.blurRegion = null;
}
// Force BackgroundEffect to re-publish the blur region on the current wl_surface.
// Clearing first bypasses Quickshell's same-Region dedup in BackgroundEffect::setBlurRegion,
// setting pendingBlurRegion=true so the next polish actually ships the region — needed
// when the underlying surface has been remapped (e.g. PanelWindow.screen change).
// Re-publish blur region after wl_surface remaps (e.g. screen change).
function kick() {
if (!targetWindow)
return;