1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-14 00:02:45 -04:00

feat(DMS FrameMode): A New Connected Unified Surface & Animation Overhaul

- Introduces Standalone & Connected Modes
- Updated Animations & Motion effects for both modes
- Numerous QOL tweaks and updates throughout the system
- Highly inspired to the OG Caelestia Shell / @Soramanew
This commit is contained in:
purian23
2026-05-03 15:38:30 -04:00
parent dd668469d7
commit fd24b4a36d
82 changed files with 12026 additions and 2905 deletions
+184 -529
View File
@@ -1,6 +1,4 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
@@ -9,11 +7,8 @@ Item {
readonly property var log: Log.scoped("DankPopout")
property string layerNamespace: "dms:popout"
property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader
property Component content: null
property Component overlayContent: null
property alias overlayLoader: overlayLoader
readonly property alias backgroundWindow: backgroundWindow
property real popupWidth: 400
property real popupHeight: 300
property real triggerX: 0
@@ -22,10 +17,10 @@ Item {
property string triggerSection: ""
property string positioning: "center"
property int animationDuration: Theme.popoutAnimationDuration
property real animationScaleCollapsed: 0.96
property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property real animationScaleCollapsed: Theme.effectScaleCollapsed
property real animationOffset: Theme.effectAnimOffset
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
property bool suspendShadowWhileResizing: false
property bool shouldBeVisible: false
property var customKeyboardFocus: null
@@ -33,9 +28,6 @@ Item {
property bool contentHandlesKeys: false
property bool fullHeightSurface: false
property bool _primeContent: false
property bool _resizeActive: false
property real _surfaceMarginLeft: 0
property real _surfaceW: 0
property real storedBarThickness: Theme.barHeight - 4
property real storedBarSpacing: 4
@@ -47,90 +39,118 @@ Item {
"rightBar": 0
})
property var screen: null
readonly property real effectiveBarThickness: {
const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4;
return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing;
}
readonly property var barBounds: {
if (!screen)
return {
"x": 0,
"y": 0,
"width": 0,
"height": 0,
"wingSize": 0
};
return SettingsData.getBarBounds(screen, effectiveBarThickness, effectiveBarPosition, storedBarConfig);
}
readonly property real barX: barBounds.x
readonly property real barY: barBounds.y
readonly property real barWidth: barBounds.width
readonly property real barHeight: barBounds.height
readonly property real barWingSize: barBounds.wingSize
property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0
signal opened
signal popoutClosed
signal backgroundClicked
property var _lastOpenedScreen: null
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
property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0
readonly property string autoBarShadowDirection: {
const section = triggerSection || "center";
switch (effectiveBarPosition) {
case SettingsData.Position.Top:
if (section === "left")
return "topLeft";
if (section === "right")
return "topRight";
return "top";
case SettingsData.Position.Bottom:
if (section === "left")
return "bottomLeft";
if (section === "right")
return "bottomRight";
return "bottom";
case SettingsData.Position.Left:
if (section === "left")
return "topLeft";
if (section === "right")
return "bottomLeft";
return "left";
case SettingsData.Position.Right:
if (section === "left")
return "topRight";
if (section === "right")
return "bottomRight";
return "right";
default:
return "top";
Loader {
id: _fallbackContentLoader
active: false
}
Loader {
id: _fallbackOverlayLoader
active: false
}
readonly property bool isClosing: impl.item ? (impl.item.isClosing ?? false) : false
readonly property real dpr: impl.item ? impl.item.dpr : 1
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 0
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 0
readonly property real alignedX: impl.item ? impl.item.alignedX : 0
readonly property real alignedY: impl.item ? impl.item.alignedY : 0
readonly property real alignedWidth: impl.item ? impl.item.alignedWidth : 0
readonly property real alignedHeight: impl.item ? impl.item.alignedHeight : 0
readonly property real maskX: impl.item ? impl.item.maskX : 0
readonly property real maskY: impl.item ? impl.item.maskY : 0
readonly property real maskWidth: impl.item ? impl.item.maskWidth : 0
readonly property real maskHeight: impl.item ? impl.item.maskHeight : 0
readonly property real barX: impl.item ? impl.item.barX : 0
readonly property real barY: impl.item ? impl.item.barY : 0
readonly property real barWidth: impl.item ? impl.item.barWidth : 0
readonly property real barHeight: impl.item ? impl.item.barHeight : 0
readonly property bool useConnectedBackend: _usesConnectedBackendForScreen(screen)
property var _resolvedBackend: null
property bool _pendingOpen: false
Timer {
id: _pendingOpenTimer
interval: 0
onTriggered: {
if (!root._pendingOpen || !impl.item)
return;
root._pendingOpen = false;
impl.item.open();
}
}
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
// Snapshot mask geometry to prevent background damage on bar updates
property real _frozenMaskX: 0
property real _frozenMaskY: 0
property real _frozenMaskWidth: 0
property real _frozenMaskHeight: 0
onUseConnectedBackendChanged: _maybeResolveBackend()
Component.onCompleted: _resolvedBackend = _backendForScreen(screen)
Connections {
target: SettingsData
function onConnectedFrameModeActiveChanged() {
root._maybeResolveBackend();
}
function onFrameScreenPreferencesChanged() {
root._maybeResolveBackend();
}
}
function _usesConnectedBackendForScreen(targetScreen) {
return SettingsData.connectedFrameModeActive && !!targetScreen && SettingsData.isScreenInPreferences(targetScreen, SettingsData.frameScreenPreferences);
}
function _backendForScreen(targetScreen) {
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);
}
function _resolveBackendForScreen(targetScreen) {
const backend = _backendForScreen(targetScreen);
if (_resolvedBackend === backend)
return;
if (impl.item && (impl.item.shouldBeVisible || impl.item.isClosing))
return;
_resolvedBackend = backend;
}
function open() {
_maybeResolveBackend();
if (impl.item) {
_pendingOpen = false;
impl.item.open();
return;
}
_pendingOpen = true;
}
function close() {
_pendingOpen = false;
_pendingOpenTimer.stop();
if (impl.item)
impl.item.close();
}
function toggle() {
(shouldBeVisible || _pendingOpen) ? close() : open();
}
function setBarContext(position, bottomGap) {
effectiveBarPosition = position !== undefined ? position : 0;
effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0;
}
function primeContent() {
_primeContent = true;
}
function clearPrimedContent() {
_primeContent = false;
}
function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
triggerX = x;
triggerY = y;
@@ -147,477 +167,112 @@ Item {
adjacentBarInfo = SettingsData.getAdjacentBarInfo(targetScreen, pos, barConfig);
setBarContext(pos, bottomGap);
_resolveBackendForScreen(targetScreen);
}
readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab
function updateSurfacePosition() {
if (useBackgroundWindow && shouldBeVisible) {
_surfaceMarginLeft = alignedX - shadowBuffer;
_surfaceW = alignedWidth + shadowBuffer * 2;
}
if (impl.item && typeof impl.item.updateSurfacePosition === "function")
impl.item.updateSurfacePosition();
}
function open() {
if (!screen)
Loader {
id: impl
active: root.screen !== null
sourceComponent: root._resolvedBackend
onItemChanged: if (item)
root._wireBackend(item)
}
Component {
id: standaloneComp
DankPopoutStandalone {}
}
Component {
id: connectedComp
DankPopoutConnected {}
}
function _wireBackend(it) {
if (!it)
return;
closeTimer.stop();
// Snapshot mask geometry
_frozenMaskX = maskX;
_frozenMaskY = maskY;
_frozenMaskWidth = maskWidth;
_frozenMaskHeight = maskHeight;
it.popoutHandle = root;
it.layerNamespace = Qt.binding(() => root.layerNamespace);
it.content = Qt.binding(() => root.content);
it.overlayContent = Qt.binding(() => root.overlayContent);
it.popupWidth = Qt.binding(() => root.popupWidth);
it.popupHeight = Qt.binding(() => root.popupHeight);
it.triggerX = Qt.binding(() => root.triggerX);
it.triggerY = Qt.binding(() => root.triggerY);
it.triggerWidth = Qt.binding(() => root.triggerWidth);
it.triggerSection = Qt.binding(() => root.triggerSection);
it.positioning = Qt.binding(() => root.positioning);
it.animationDuration = Qt.binding(() => root.animationDuration);
it.animationScaleCollapsed = Qt.binding(() => root.animationScaleCollapsed);
it.animationOffset = Qt.binding(() => root.animationOffset);
it.animationEnterCurve = Qt.binding(() => root.animationEnterCurve);
it.animationExitCurve = Qt.binding(() => root.animationExitCurve);
it.suspendShadowWhileResizing = Qt.binding(() => root.suspendShadowWhileResizing);
it.customKeyboardFocus = Qt.binding(() => root.customKeyboardFocus);
it.backgroundInteractive = Qt.binding(() => root.backgroundInteractive);
it.contentHandlesKeys = Qt.binding(() => root.contentHandlesKeys);
it.fullHeightSurface = Qt.binding(() => root.fullHeightSurface);
it.storedBarThickness = Qt.binding(() => root.storedBarThickness);
it.storedBarSpacing = Qt.binding(() => root.storedBarSpacing);
it.storedBarConfig = Qt.binding(() => root.storedBarConfig);
it.adjacentBarInfo = Qt.binding(() => root.adjacentBarInfo);
it.screen = Qt.binding(() => root.screen);
it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition);
it.effectiveBarBottomGap = Qt.binding(() => root.effectiveBarBottomGap);
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) {
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
}
_lastOpenedScreen = screen;
shouldBeVisible = true;
if (useBackgroundWindow) {
_surfaceMarginLeft = alignedX - shadowBuffer;
_surfaceW = alignedWidth + shadowBuffer * 2;
}
Qt.callLater(() => {
if (shouldBeVisible && screen) {
if (useBackgroundWindow)
backgroundWindow.visible = true;
contentWindow.visible = true;
PopoutManager.showPopout(root);
opened();
}
});
it.shouldBeVisible = root.shouldBeVisible;
if (root._primeContent && typeof it.primeContent === "function")
it.primeContent();
if (_pendingOpen)
_pendingOpenTimer.restart();
}
function close() {
shouldBeVisible = false;
function primeContent() {
_primeContent = true;
if (impl.item)
impl.item.primeContent();
}
function clearPrimedContent() {
_primeContent = false;
PopoutManager.popoutChanged();
closeTimer.restart();
}
function toggle() {
shouldBeVisible ? close() : open();
if (impl.item)
impl.item.clearPrimedContent();
}
Connections {
target: Quickshell
function onScreensChanged() {
if (!shouldBeVisible || !screen)
return;
const currentScreenName = screen.name;
let screenStillExists = false;
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === currentScreenName) {
screenStillExists = true;
break;
}
}
if (!screenStillExists) {
close();
}
target: root
function onShouldBeVisibleChanged() {
if (impl.item && impl.item.shouldBeVisible !== root.shouldBeVisible)
impl.item.shouldBeVisible = root.shouldBeVisible;
}
}
Timer {
id: closeTimer
interval: animationDuration
onTriggered: {
if (!shouldBeVisible) {
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
PopoutManager.hidePopout(root);
popoutClosed();
}
}
}
Connections {
target: impl.item
ignoreUnknownSignals: true
readonly property real screenWidth: screen ? screen.width : 0
readonly property real screenHeight: screen ? screen.height : 0
readonly property real dpr: screen ? screen.devicePixelRatio : 1
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: Math.max(0, animationOffset)
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
onAlignedHeightChanged: {
if (!suspendShadowWhileResizing || !shouldBeVisible)
return;
_resizeActive = true;
resizeSettleTimer.restart();
}
onShouldBeVisibleChanged: {
if (!shouldBeVisible) {
_resizeActive = false;
resizeSettleTimer.stop();
}
}
Timer {
id: resizeSettleTimer
interval: 80
repeat: false
onTriggered: root._resizeActive = false
}
readonly property real alignedX: Theme.snap((() => {
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
switch (effectiveBarPosition) {
case SettingsData.Position.Left:
return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX));
case SettingsData.Position.Right:
return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX - popupWidth));
default:
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
const minX = adjacentBarInfo.leftBar > 0 ? adjacentBarInfo.leftBar : popupGap;
const maxX = screenWidth - popupWidth - (adjacentBarInfo.rightBar > 0 ? adjacentBarInfo.rightBar : popupGap);
return Math.max(minX, Math.min(maxX, rawX));
}
})(), dpr)
readonly property real alignedY: Theme.snap((() => {
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
switch (effectiveBarPosition) {
case SettingsData.Position.Bottom:
return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY - popupHeight));
case SettingsData.Position.Top:
return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY));
default:
const rawY = triggerY - (popupHeight / 2);
const minY = adjacentBarInfo.topBar > 0 ? adjacentBarInfo.topBar : popupGap;
const maxY = screenHeight - popupHeight - (adjacentBarInfo.bottomBar > 0 ? adjacentBarInfo.bottomBar : popupGap);
return Math.max(minY, Math.min(maxY, rawY));
}
})(), 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: {
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);
}
PanelWindow {
id: backgroundWindow
screen: root.screen
visible: false
color: "transparent"
Component.onCompleted: {
if (typeof updatesEnabled !== "undefined" && !root.overlayContent)
updatesEnabled = false;
function onShouldBeVisibleChanged() {
if (impl.item && root.shouldBeVisible !== impl.item.shouldBeVisible)
root.shouldBeVisible = impl.item.shouldBeVisible;
}
WlrLayershell.namespace: root.layerNamespace + ":background"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
function onOpened() {
root.opened();
}
mask: Region {
item: maskRect
function onPopoutClosed() {
root.popoutClosed();
root._maybeResolveBackend();
}
Rectangle {
id: maskRect
visible: false
color: "transparent"
x: root._frozenMaskX
y: root._frozenMaskY
width: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
height: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 0
}
MouseArea {
x: root._frozenMaskX
y: root._frozenMaskY
width: root._frozenMaskWidth
height: root._frozenMaskHeight
hoverEnabled: false
enabled: shouldBeVisible && backgroundInteractive
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + root._frozenMaskX;
const clickY = mouse.y + root._frozenMaskY;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
backgroundClicked();
}
}
Loader {
id: overlayLoader
anchors.fill: parent
active: root.overlayContent !== null && backgroundWindow.visible
sourceComponent: root.overlayContent
}
}
PanelWindow {
id: contentWindow
screen: root.screen
visible: false
color: "transparent"
WindowBlur {
id: popoutBlur
targetWindow: contentWindow
readonly property real s: Math.min(1, contentContainer.scaleValue)
blurX: contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.width * s : 0
blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.height * s : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: {
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
case "bottom":
log.warn("'bottom' layer is not valid for popouts. Defaulting to 'top' layer.");
return WlrLayershell.Top;
case "background":
log.warn("'background' layer is not valid for popouts. Defaulting to 'top' layer.");
return WlrLayershell.Top;
case "overlay":
return WlrLayershell.Overlay;
default:
return WlrLayershell.Top;
}
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
anchors {
left: true
top: true
right: !useBackgroundWindow
bottom: _fullHeight || !useBackgroundWindow
}
WlrLayershell.margins {
left: useBackgroundWindow ? root._surfaceMarginLeft : 0
top: (useBackgroundWindow && !_fullHeight) ? (root.alignedY - shadowBuffer) : 0
}
implicitWidth: useBackgroundWindow ? root._surfaceW : 0
implicitHeight: (useBackgroundWindow && !_fullHeight) ? (root.alignedHeight + shadowBuffer * 2) : 0
mask: useBackgroundWindow ? contentInputMask : null
Region {
id: contentInputMask
item: contentMaskRect
}
Item {
id: contentMaskRect
visible: false
x: contentContainer.x
y: contentContainer.y
width: shouldBeVisible ? root.alignedWidth : 0
height: shouldBeVisible ? root.alignedHeight : 0
}
MouseArea {
anchors.fill: parent
enabled: !useBackgroundWindow && shouldBeVisible && backgroundInteractive
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
z: -1
onClicked: mouse => {
const clickX = mouse.x;
const clickY = mouse.y;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
backgroundClicked();
}
}
Item {
id: contentContainer
x: useBackgroundWindow ? shadowBuffer : root.alignedX
y: (useBackgroundWindow && !contentWindow._fullHeight) ? shadowBuffer : root.alignedY
width: root.alignedWidth
height: root.alignedHeight
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
property real animX: 0
property real animY: 0
property real scaleValue: root.animationScaleCollapsed
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
Connections {
target: root
function onShouldBeVisibleChanged() {
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr);
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
}
}
Behavior on animX {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on animY {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on scaleValue {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
ElevationShadow {
id: shadowSource
width: parent.width
height: parent.height
opacity: contentWrapper.opacity
scale: contentWrapper.scale
x: contentWrapper.x
y: contentWrapper.y
level: root.shadowLevel
direction: root.effectiveShadowDirection
fallbackOffset: root.shadowFallbackOffset
targetRadius: Theme.cornerRadius
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !BlurService.enabled
}
Item {
id: contentWrapper
anchors.centerIn: parent
width: parent.width
height: parent.height
opacity: shouldBeVisible ? 1 : 0
visible: opacity > 0
scale: contentContainer.scaleValue
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
layer.enabled: contentWrapper.opacity < 1
layer.smooth: false
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
Behavior on opacity {
NumberAnimation {
duration: animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Loader {
id: contentLoader
anchors.fill: parent
active: root._primeContent || shouldBeVisible || contentWindow.visible
asynchronous: false
}
}
Rectangle {
width: parent.width
height: parent.height
x: contentWrapper.x
y: contentWrapper.y
opacity: contentWrapper.opacity
scale: contentWrapper.scale
visible: contentWrapper.visible
radius: Theme.cornerRadius
antialiasing: true
color: "transparent"
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
z: 100
}
}
Item {
id: focusHelper
parent: contentContainer
anchors.fill: parent
visible: !root.contentHandlesKeys
enabled: !root.contentHandlesKeys
focus: !root.contentHandlesKeys
Keys.onPressed: event => {
if (root.contentHandlesKeys)
return;
if (event.key === Qt.Key_Escape) {
close();
event.accepted = true;
}
}
function onBackgroundClicked() {
root.backgroundClicked();
}
}
}