control center: one surface under the edit strip & backdrops/frame sheet fixes

(cherry picked from commit e48c1221e7)
This commit is contained in:
purian23 committed 2026-10-29 13:57:12 -04:00
1 parent 3df95c8f4f
commit 974c4d3167
11 files changed
+86 -45

No files matched your search

Generated
+3 -3
View File
@@ -3,11 +3,11 @@
"dank-qml-common": {
"flake": false,
"locked": {
"lastModified": 1793224384,
"narHash": "sha256-mJaCE9rdBd/HyYSCPomQM/uX+1YNmFfV64bMIdUSiL0=",
"lastModified": 1793296417,
"narHash": "sha256-gCK9N6GPq+3HdCXrikVSRcE3/u7HWRJHJpcfE2cpH3Q=",
"owner": "duxlabs-dev",
"repo": "dux-qml-common",
"rev": "09ac4aba511f10636c273e11028fe092196c78ba",
"rev": "2065795d9382b03c71e6f316b4955c6a74051934",
"type": "github"
},
"original": {
@@ -18,13 +18,16 @@ function isVertical(side) {
return side === "left" || side === "right";
}
// Overflow chrome (the edit strip) is part of the silhouette, so it rolls out with the body.
function bodyRect(descriptor, dpr) {
var source = descriptor && descriptor.bodyRect ? descriptor.bodyRect : descriptor || {};
var overflowTop = Math.max(0, _number(descriptor && descriptor.overflowTop, 0));
var overflowBottom = Math.max(0, _number(descriptor && descriptor.overflowBottom, 0));
return {
"x": snap(source.x !== undefined ? source.x : source.bodyX, dpr),
"y": snap(source.y !== undefined ? source.y : source.bodyY, dpr),
"y": snap(_number(source.y !== undefined ? source.y : source.bodyY, 0) - overflowTop, 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))
"height": Math.max(0, snap(_number(source.height !== undefined ? source.height : source.bodyH, 0) + overflowTop + overflowBottom, dpr))
};
}
@@ -22,7 +22,6 @@ Item {
property bool overTrash: false
property color backgroundColor: CcMetrics.dialogColor
property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
// Shoulders fill outside the panel's corner arcs so a translucent fill never doubles up.
property bool joined: false
property bool above: false
readonly property real leftRadius: above ? cornerRadii.x : cornerRadii.w
@@ -32,13 +31,11 @@ Item {
readonly property string outline: {
const w = width;
const h = height;
const lw = Math.min(leftRadius, w / 2);
const rw = Math.min(rightRadius, w / 2);
const lf = Math.min(lw, h / 2);
const rf = Math.min(rw, h / 2);
const lf = Math.min(leftRadius, w / 2, h / 2);
const rf = Math.min(rightRadius, w / 2, h / 2);
const y = v => above ? h - v : v;
const s = flag => above ? 1 - flag : flag;
const joinedEdge = joined ? `M 0 ${y(-lw)} A ${lw} ${lw} 0 0 ${s(0)} ${lw} ${y(0)} L ${w - rw} ${y(0)} A ${rw} ${rw} 0 0 ${s(0)} ${w} ${y(-rw)}` : `M 0 ${y(lf)} A ${lf} ${lf} 0 0 ${s(1)} ${lf} ${y(0)} L ${w - rf} ${y(0)} A ${rf} ${rf} 0 0 ${s(1)} ${w} ${y(rf)}`;
const joinedEdge = joined ? `M 0 ${y(0)} L ${w} ${y(0)}` : `M 0 ${y(lf)} A ${lf} ${lf} 0 0 ${s(1)} ${lf} ${y(0)} L ${w - rf} ${y(0)} A ${rf} ${rf} 0 0 ${s(1)} ${w} ${y(rf)}`;
return `${joinedEdge} L ${w} ${y(h - rf)} A ${rf} ${rf} 0 0 ${s(1)} ${w - rf} ${y(h)} L ${lf} ${y(h)} A ${lf} ${lf} 0 0 ${s(1)} 0 ${y(h - lf)} Z`;
}
@@ -98,7 +98,8 @@ DBottomSheet {
width: CcMetrics.previewSize
height: width
radius: item.modelData.id === "user" ? Theme.fullRadius(width, height) : Theme.cornerRadiusLIncreased
color: CcMetrics.iconBoxInactiveColor
// Solid: translucent, it vanishes against the card in light mode.
color: Theme.chipSurfaceNested
DIcon {
anchors.centerIn: parent
@@ -186,7 +187,7 @@ DBottomSheet {
width: Theme.avatarSize
height: width
radius: Theme.fullRadius(width, height)
color: CcMetrics.iconBoxInactiveColor
color: Theme.chipSurfaceNested
DIcon {
anchors.centerIn: parent
@@ -68,6 +68,8 @@ FocusScope {
readonly property bool panelResizing: panelResizer.resizing
readonly property real sheetContentWidth: host.sheetContentWidth ?? CcMetrics.sheetWidthFor(gridColumns)
readonly property vector4d surfaceCornerRadii: host.surfaceCornerRadii ?? Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
// A dim can't follow a frame-attached body past its square corners; the covered content is faded out anyway.
readonly property bool sheetsDim: !CcMetrics.hideCoveredContent || Math.min(surfaceCornerRadii.x, surfaceCornerRadii.y, surfaceCornerRadii.z, surfaceCornerRadii.w) > 0
readonly property int gridColumnCap: host.gridColumnCap ?? CcMetrics.columnCapFor((host.triggerScreen?.width ?? CcMetrics.sheetWidthDefault + Theme.spacingL * 2) - Theme.spacingL * 2)
readonly property int gridColumns: host.gridColumns ?? Math.min(CcMetrics.gridColumns, gridColumnCap)
readonly property real availableGridHeight: (host.availableHeight ?? (host.triggerScreen?.height ?? CcMetrics.fallbackScreenHeight) - CcMetrics.maxHeightInset) - CcMetrics.sheetPadding * 2 - editRoom
@@ -377,8 +379,8 @@ FocusScope {
cornerResize: root.panelResizing ? resizeSide > 0 : root.panelResizer.sideMovable(1, root.gridColumns)
horizontalResize: true
removable: false
cornerRadius: Theme.windowRadius
cornerRadii: root.surfaceCornerRadii
// Frame-attached corners are square, but the ring keeps the surface radius.
cornerRadius: Math.max(root.surfaceCornerRadii.x, root.surfaceCornerRadii.y, root.surfaceCornerRadii.z, root.surfaceCornerRadii.w)
buttonSize: Theme.iconSize
iconSize: PopoutMetrics.chromeIconSize
resizing: root.panelResizing
@@ -533,7 +535,7 @@ FocusScope {
y: root.editRoom > 0 ? Math.max(root.height - root.editRoom, CcMetrics.sheetPadding * 2 + root.heldGridHeight) + (root.editRoom - height) / 2 : (root.stripAbove ? -height : root.height)
width: root.sheetContentWidth
height: implicitHeight
// Below the edit rings so the shoulder fill never covers them.
// Below the edit rings so the trash fill never covers them.
z: -1
editMode: root.host.editMode
enabled: detailBand.shownSection === "" && !root.widgetSheetOpen
@@ -543,7 +545,8 @@ FocusScope {
transientSurfaceTracker: root.host.transientSurfaceTracker
joined: root.editRoom === 0
above: root.stripAbove
backgroundColor: joined ? (root.host.surfaceColor ?? CcMetrics.dialogColor) : Theme.foregroundColor(Theme.cardSurface)
// Joined, the popout host paints the surface under the strip.
backgroundColor: joined ? "transparent" : Theme.foregroundColor(Theme.cardSurface)
cornerRadii: root.surfaceCornerRadii
opacity: body.opacity
canUndo: root.canUndo
@@ -563,6 +566,7 @@ FocusScope {
active: false
sourceComponent: BluetoothCodecSelector {
cornerRadii: root.surfaceCornerRadii
dimsBackdrop: root.sheetsDim
onDismissed: Qt.callLater(root.releaseSheet, codecSelectorLoader)
}
}
@@ -575,6 +579,7 @@ FocusScope {
active: false
sourceComponent: AudioPortSelector {
cornerRadii: root.surfaceCornerRadii
dimsBackdrop: root.sheetsDim
onDismissed: Qt.callLater(root.releaseSheet, portSelectorLoader)
}
}
@@ -591,6 +596,8 @@ FocusScope {
id: widgetSheetLoader
anchors.fill: parent
anchors.topMargin: editActions.joined && root.stripAbove ? -editActions.height : 0
anchors.bottomMargin: editActions.joined && !root.stripAbove ? -editActions.height : 0
z: CcMetrics.overlayZ
active: root.host.editMode
asynchronous: true
@@ -602,6 +609,7 @@ FocusScope {
}
sourceComponent: CcWidgetSheet {
scrimRadii: root.surfaceCornerRadii
scrimVisible: root.sheetsDim
surfaceColor: CcMetrics.dialogColor
widgets: {
const existingIds = root.placedWidgetIds.split(",");
@@ -17,6 +17,7 @@ Item {
property real panelWidth: CcMetrics.dialogWidth
property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
property Item backdrop: null
property bool dimsBackdrop: true
readonly property real presence: panel.opacity
default property alias content: contentSlot.data
property alias showScrollBar: contentFlickable.showScrollBar
@@ -53,6 +54,7 @@ Item {
topRightRadius: root.cornerRadii.y
bottomRightRadius: root.cornerRadii.z
bottomLeftRadius: root.cornerRadii.w
visible: root.dimsBackdrop
color: Theme.withAlpha(Theme.backdropColor, Theme.scrimAlpha)
opacity: root.shown ? 1 : 0
+1 -7
View File
@@ -563,13 +563,7 @@ PanelWindow {
readonly property bool _active: win._blurSurfacesActive && win._popoutDescriptor.visible
// Covers chrome hung off the body; the cap and connectors stay on the body.
readonly property var _blur: win._seamPaddedBlurRect({
"x": win._popoutBodyGeometry.x,
"y": win._popoutBodyGeometry.y - win._popoutDescriptor.overflowTop,
"width": win._popoutBodyGeometry.width,
"height": win._popoutBodyGeometry.height + win._popoutDescriptor.overflowTop + win._popoutDescriptor.overflowBottom
})
readonly property var _blur: win._seamPaddedBlurRect(win._popoutBodyGeometry)
radius: win._surfaceRadius
x: _active ? _blur.x : 0
@@ -92,7 +92,7 @@ Scope {
Rectangle {
id: background
anchors.fill: parent
color: "black"
color: Theme.backdropColor
opacity: overviewScope.overviewOpen ? 0.5 : 0
Behavior on opacity {
+27 -19
View File
@@ -233,7 +233,7 @@ Item {
if (barSide !== "top" && barSide !== "bottom")
return contentContainer.animY;
const extent = Math.max(0, root.pubBodyH);
const extent = Math.max(0, root.pubBodyH + root.topOverflow + root.bottomOverflow);
const progress = Math.min(1, Math.abs(contentContainer.animY) / Math.max(1, _chromeAnimTravelY));
const offset = Theme.snap(extent * progress, root.dpr);
return contentContainer.animY < 0 ? -offset : offset;
@@ -995,7 +995,11 @@ Item {
return;
if ((!hoverDismissEnabled && !isClosing) || !ConnectedModeState.hasSurfaceOwner(root.screen.name, "popout", chromeLease.claimId))
return;
const body = SurfaceGeometry.animatedBodyRect(_connectedChromeState(), root.dpr);
// Hand off the bare body; the overflow stays on this descriptor.
const state = _connectedChromeState();
state.overflowTop = 0;
state.overflowBottom = 0;
const body = SurfaceGeometry.animatedBodyRect(state, root.dpr);
const traveling = morphTravelEnabled && !morph.running;
const rect = traveling ? root._boundTravel(travelSpring.value) : Qt.rect(body.x, body.y, Math.max(1, body.width), Math.max(1, body.height));
const velocity = traveling ? travelSpring.velocity : Qt.vector4d(0, 0, 0, 0);
@@ -1681,7 +1685,7 @@ Item {
readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect
readonly property real directionalTravelX: Math.max(root.animationOffset, root.renderedAlignedWidth + Theme.spacingL)
readonly property real directionalTravelY: Math.max(root.animationOffset, root.renderedAlignedHeight + Theme.spacingL)
readonly property real directionalTravelY: Math.max(root.animationOffset, root.renderedAlignedHeight + root.topOverflow + root.bottomOverflow + Theme.spacingL)
readonly property real depthTravel: Math.max(root.animationOffset * 0.7, 28)
readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0))
readonly property real horizontalConnectorExtent: root.usesConnectedSurfaceChrome && (barTop || barBottom) ? Theme.connectedCornerRadius : 0
@@ -1850,13 +1854,14 @@ Item {
readonly property real s: Math.min(1, contentContainer.scaleValue)
readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome
readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0
readonly property real _overflowExtent: root.topOverflow + root.bottomOverflow
readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height - _overflowExtent, Math.min(contentContainer.animY, contentContainer.height + _overflowExtent)) : 0
readonly property real _dxClamp: (contentContainer.barLeft || contentContainer.barRight) ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0
blurX: trackBlurFromBarEdge ? contentContainer.x + (contentContainer.barRight ? _dxClamp : 0) : contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) - contentContainer.horizontalConnectorExtent * s
blurY: (trackBlurFromBarEdge ? contentContainer.y + (contentContainer.barBottom ? _dyClamp : 0) : contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) - contentContainer.verticalConnectorExtent * s) - root.topOverflow
blurWidth: root.shouldBeVisible ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.width - Math.abs(_dxClamp)) : (contentContainer.width + contentContainer.horizontalConnectorExtent * 2) * s) : 0
blurHeight: root.shouldBeVisible ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.height - Math.abs(_dyClamp)) : (contentContainer.height + contentContainer.verticalConnectorExtent * 2) * s) + root.topOverflow + root.bottomOverflow : 0
blurHeight: root.shouldBeVisible ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.height + _overflowExtent - Math.abs(_dyClamp)) : (contentContainer.height + contentContainer.verticalConnectorExtent * 2) * s + _overflowExtent) : 0
blurRadius: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : Theme.windowRadius
}
@@ -1914,11 +1919,11 @@ Item {
ElevationShadow {
visible: !root.usesConnectedSurfaceChrome
width: rollOutAdjuster.baseWidth
height: rollOutAdjuster.baseHeight
height: rollOutAdjuster.baseHeight + root.topOverflow + root.bottomOverflow
opacity: contentWrapper.publishedOpacity
scale: contentWrapper.scale
x: contentWrapper.x
y: contentWrapper.y
y: contentWrapper.y - root.topOverflow
level: root.shadowLevel
direction: root.effectiveShadowDirection
fallbackOffset: root.shadowFallbackOffset
@@ -1927,9 +1932,8 @@ Item {
topRightRadius: chrome.surfaceTopRightRadius
bottomLeftRadius: chrome.surfaceBottomLeftRadius
bottomRightRadius: chrome.surfaceBottomRightRadius
targetColor: chrome.surfaceColor
borderColor: chrome.surfaceBorderColor
borderWidth: chrome.surfaceBorderWidth
// Shadow only; contentWrapper paints the fill.
targetColor: "transparent"
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome
}
@@ -1950,11 +1954,11 @@ Item {
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
height: rollOutAdjuster.baseHeight + extraTop * 2 + root.topOverflow + root.bottomOverflow
opacity: contentWrapper.publishedOpacity
scale: contentWrapper.scale
x: contentWrapper.x - extraLeft
y: contentWrapper.y - extraTop
y: contentWrapper.y - extraTop - root.topOverflow
ShaderEffect {
anchors.fill: parent
@@ -1970,7 +1974,7 @@ Item {
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 bodyRect: Qt.vector4d((contentContainer.barLeft ? 0 : localChrome.pad) + localChrome.extraLeft, (contentContainer.barTop ? 0 : localChrome.pad) + localChrome.extraTop, rollOutAdjuster.baseWidth, rollOutAdjuster.baseHeight + root.topOverflow + root.bottomOverflow)
property vector4d cornerRadius: Qt.vector4d(chrome.surfaceTopLeftRadius, chrome.surfaceTopRightRadius, chrome.surfaceBottomRightRadius, chrome.surfaceBottomLeftRadius)
property vector4d edgeParam: Qt.vector4d(contentContainer.barTop ? 0 : (contentContainer.barBottom ? 1 : (contentContainer.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
}
@@ -2017,7 +2021,9 @@ Item {
visible: !root.usesConnectedSurfaceChrome
Rectangle {
anchors.fill: parent
y: -root.topOverflow
width: parent.width
height: parent.height + root.topOverflow + root.bottomOverflow
antialiasing: true
topLeftRadius: chrome.surfaceTopLeftRadius
topRightRadius: chrome.surfaceTopRightRadius
@@ -2126,11 +2132,11 @@ Item {
parent: root._fluidMotionActive ? chrome : contentClip.contentItem
z: root._fluidMotionActive ? -1 : 0
width: root._fluidMotionActive ? chrome.fluidBody.width : chrome.width
height: root._fluidMotionActive ? chrome.fluidBody.height : chrome.height + root.topOverflow + root.bottomOverflow
height: (root._fluidMotionActive ? chrome.fluidBody.height : chrome.height) + root.topOverflow + root.bottomOverflow
opacity: root._fluidMotionActive ? FluidGeometry.chromeOpacity(morph.value) : contentWrapper.publishedOpacity
scale: root.directionalRevealActive ? 1 : contentWrapper.scale
x: root._fluidMotionActive ? chrome.fluidBody.x : (root.directionalRevealActive ? 0 : contentWrapper.x)
y: root._fluidMotionActive ? chrome.fluidBody.y : (root.directionalRevealActive ? 0 : contentWrapper.y) - root.topOverflow
y: (root._fluidMotionActive ? chrome.fluidBody.y : (root.directionalRevealActive ? 0 : contentWrapper.y)) - root.topOverflow
level: root.shadowLevel
direction: root.effectiveShadowDirection
fallbackOffset: root.shadowFallbackOffset
@@ -2172,7 +2178,9 @@ Item {
}
Rectangle {
anchors.fill: parent
y: -root.topOverflow
width: parent.width
height: parent.height + root.topOverflow + root.bottomOverflow
visible: !root._fluidMotionActive
radius: Theme.windowRadius
color: Theme.readableSurface
@@ -2182,9 +2190,9 @@ Item {
Rectangle {
parent: root._fluidMotionActive ? chrome : contentClip.contentItem
width: root._fluidMotionActive ? chrome.fluidBody.width : chrome.width
height: root._fluidMotionActive ? chrome.fluidBody.height : chrome.height + root.topOverflow + root.bottomOverflow
height: (root._fluidMotionActive ? chrome.fluidBody.height : chrome.height) + root.topOverflow + root.bottomOverflow
x: root._fluidMotionActive ? chrome.fluidBody.x : (root.directionalRevealActive ? 0 : contentWrapper.x)
y: root._fluidMotionActive ? chrome.fluidBody.y : (root.directionalRevealActive ? 0 : contentWrapper.y) - root.topOverflow
y: (root._fluidMotionActive ? chrome.fluidBody.y : (root.directionalRevealActive ? 0 : contentWrapper.y)) - root.topOverflow
opacity: root._fluidMotionActive ? FluidGeometry.chromeOpacity(morph.value) : contentWrapper.publishedOpacity
scale: root.directionalRevealActive ? 1 : contentWrapper.scale
visible: contentWrapper.visible
@@ -0,0 +1,28 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import vm from "node:vm";
const source = readFileSync(new URL("../Common/ConnectedSurfaceGeometry.js", import.meta.url), "utf8").replace(".pragma library", "");
const geometry = vm.createContext({});
vm.runInContext(source, geometry);
test("an edit strip hung off the body rolls out with it", () => {
const body = { x: 10, y: 40, width: 300, height: 200 };
const cases = [
{ side: "top", overflowTop: 0, overflowBottom: 50, dy: -125, rect: { x: 10, y: 40, width: 300, height: 125 } },
{ side: "top", overflowTop: 0, overflowBottom: 50, dy: -250, rect: { x: 10, y: 40, width: 300, height: 0 } },
{ side: "bottom", overflowTop: 50, overflowBottom: 0, dy: 125, rect: { x: 10, y: 115, width: 300, height: 125 } },
{ side: "left", overflowTop: 0, overflowBottom: 50, dy: 0, rect: { x: 10, y: 40, width: 300, height: 250 } }
];
for (const c of cases) {
const rect = geometry.animatedBodyRect({
barSide: c.side,
bodyRect: body,
animationOffset: { x: 0, y: c.dy },
overflowTop: c.overflowTop,
overflowBottom: c.overflowBottom
}, 1);
assert.deepEqual({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }, c.rect, `${c.side} dy=${c.dy}`);
}
});