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

Add Directional Motion options

This commit is contained in:
purian23
2026-03-04 10:14:00 -05:00
parent 4784087dc2
commit f2bc348b62
8 changed files with 376 additions and 202 deletions

View File

@@ -15,9 +15,12 @@ Singleton {
if (typeof SettingsData === "undefined") if (typeof SettingsData === "undefined")
return Anims.expressiveDefaultSpatial; return Anims.expressiveDefaultSpatial;
switch (SettingsData.animationVariant) { switch (SettingsData.animationVariant) {
case 1: return Anims.standardDecel; case 1:
case 2: return Anims.expressiveFastSpatial; return Anims.standardDecel;
default: return Anims.expressiveDefaultSpatial; case 2:
return Anims.expressiveFastSpatial;
default:
return Anims.expressiveDefaultSpatial;
} }
} }
@@ -25,9 +28,12 @@ Singleton {
if (typeof SettingsData === "undefined") if (typeof SettingsData === "undefined")
return Anims.emphasized; return Anims.emphasized;
switch (SettingsData.animationVariant) { switch (SettingsData.animationVariant) {
case 1: return Anims.standard; case 1:
case 2: return Anims.emphasized; return Anims.standard;
default: return Anims.emphasized; case 2:
return Anims.emphasized;
default:
return Anims.emphasized;
} }
} }
@@ -64,7 +70,7 @@ Singleton {
if (SettingsData.animationVariant === 1) if (SettingsData.animationVariant === 1)
return Anims.standardDecel; return Anims.standardDecel;
if (SettingsData.animationVariant === 2) if (SettingsData.animationVariant === 2)
return Anims.standardDecel; return Anims.expressiveFastSpatial;
return Anims.standardDecel; return Anims.standardDecel;
} }
return variantEnterCurve; return variantEnterCurve;
@@ -83,26 +89,35 @@ Singleton {
} }
readonly property real variantEnterDurationFactor: { readonly property real variantEnterDurationFactor: {
if (typeof SettingsData === "undefined") return 1.0; if (typeof SettingsData === "undefined")
return 1.0;
switch (SettingsData.animationVariant) { switch (SettingsData.animationVariant) {
case 1: return 0.9; case 1:
case 2: return 1.08; return 0.9;
default: return 1.0; case 2:
return 1.08;
default:
return 1.0;
} }
} }
readonly property real variantExitDurationFactor: { readonly property real variantExitDurationFactor: {
if (typeof SettingsData === "undefined") return 1.0; if (typeof SettingsData === "undefined")
return 1.0;
switch (SettingsData.animationVariant) { switch (SettingsData.animationVariant) {
case 1: return 0.85; case 1:
case 2: return 0.92; return 0.85;
default: return 1.0; case 2:
return 0.92;
default:
return 1.0;
} }
} }
// Fluent: opacity at ~55% of duration; Material/Dynamic: 1:1 with position // Fluent: opacity at ~55% of duration; Material/Dynamic: 1:1 with position
readonly property real variantOpacityDurationScale: { readonly property real variantOpacityDurationScale: {
if (typeof SettingsData === "undefined") return 1.0; if (typeof SettingsData === "undefined")
return 1.0;
return SettingsData.animationVariant === 1 ? 0.55 : 1.0; return SettingsData.animationVariant === 1 ? 0.55 : 1.0;
} }
@@ -112,11 +127,15 @@ Singleton {
} }
function variantExitCleanupPadding() { function variantExitCleanupPadding() {
if (typeof SettingsData === "undefined") return 50; if (typeof SettingsData === "undefined")
return 50;
switch (SettingsData.motionEffect) { switch (SettingsData.motionEffect) {
case 1: return 8; case 1:
case 2: return 24; return 8;
default: return 50; case 2:
return 24;
default:
return 50;
} }
} }
@@ -128,20 +147,28 @@ Singleton {
readonly property bool isDepthEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 2 readonly property bool isDepthEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 2
readonly property real effectScaleCollapsed: { readonly property real effectScaleCollapsed: {
if (typeof SettingsData === "undefined") return 0.96; if (typeof SettingsData === "undefined")
return 0.96;
switch (SettingsData.motionEffect) { switch (SettingsData.motionEffect) {
case 1: return 1.0; case 1:
case 2: return 0.88; return 1.0;
default: return 0.96; case 2:
return 0.88;
default:
return 0.96;
} }
} }
readonly property real effectAnimOffset: { readonly property real effectAnimOffset: {
if (typeof SettingsData === "undefined") return 16; if (typeof SettingsData === "undefined")
return 16;
switch (SettingsData.motionEffect) { switch (SettingsData.motionEffect) {
case 1: return 144; case 1:
case 2: return 56; return 144;
default: return 16; case 2:
return 56;
default:
return 16;
} }
} }
} }

View File

@@ -25,6 +25,6 @@ Singleton {
// Used by AnimVariants for variant/effect logic // Used by AnimVariants for variant/effect logic
readonly property var expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] readonly property var expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
readonly property var expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] readonly property var expressiveFastSpatial: [0.34, 1.5, 0.2, 1.0, 1.0, 1.0]
readonly property var expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] readonly property var expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
} }

View File

@@ -184,6 +184,8 @@ Singleton {
onAnimationVariantChanged: saveSettings() onAnimationVariantChanged: saveSettings()
property int motionEffect: SettingsData.AnimationEffect.Standard property int motionEffect: SettingsData.AnimationEffect.Standard
onMotionEffectChanged: saveSettings() onMotionEffectChanged: saveSettings()
property int directionalAnimationMode: 0
onDirectionalAnimationModeChanged: saveSettings()
property bool m3ElevationEnabled: true property bool m3ElevationEnabled: true
onM3ElevationEnabledChanged: saveSettings() onM3ElevationEnabledChanged: saveSettings()
property int m3ElevationIntensity: 12 property int m3ElevationIntensity: 12

View File

@@ -51,6 +51,7 @@ var SPEC = {
enableRippleEffects: { def: true }, enableRippleEffects: { def: true },
animationVariant: { def: 0 }, animationVariant: { def: 0 },
motionEffect: { def: 0 }, motionEffect: { def: 0 },
directionalAnimationMode: { def: 0 },
m3ElevationEnabled: { def: true }, m3ElevationEnabled: { def: true },
m3ElevationIntensity: { def: 12 }, m3ElevationIntensity: { def: 12 },
m3ElevationOpacity: { def: 30 }, m3ElevationOpacity: { def: 30 },

View File

@@ -50,8 +50,7 @@ Item {
readonly property alias clickCatcher: clickCatcher readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground readonly property bool useSingleWindow: CompositorService.isHyprland
readonly property bool _needsFullscreenMotion: !useSingleWindow && (Theme.isDirectionalEffect || Theme.isDepthEffect)
signal opened signal opened
signal dialogClosed signal dialogClosed
@@ -68,12 +67,12 @@ Item {
const focusedScreen = CompositorService.getFocusedScreen(); const focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) { if (focusedScreen) {
contentWindow.screen = focusedScreen; contentWindow.screen = focusedScreen;
if (!useSingleWindow && !_needsFullscreenMotion) if (!useSingleWindow)
clickCatcher.screen = focusedScreen; clickCatcher.screen = focusedScreen;
} }
if (Theme.isDirectionalEffect) { if (Theme.isDirectionalEffect || root.useBackground) {
if (!useSingleWindow && !_needsFullscreenMotion) if (!useSingleWindow)
clickCatcher.visible = true; clickCatcher.visible = true;
contentWindow.visible = true; contentWindow.visible = true;
} }
@@ -82,7 +81,7 @@ Item {
Qt.callLater(() => { Qt.callLater(() => {
animationsEnabled = true; animationsEnabled = true;
shouldBeVisible = true; shouldBeVisible = true;
if (!useSingleWindow && !_needsFullscreenMotion && !clickCatcher.visible) if (!useSingleWindow && !clickCatcher.visible)
clickCatcher.visible = true; clickCatcher.visible = true;
if (!contentWindow.visible) if (!contentWindow.visible)
contentWindow.visible = true; contentWindow.visible = true;
@@ -105,7 +104,7 @@ Item {
ModalManager.closeModal(root); ModalManager.closeModal(root);
closeTimer.stop(); closeTimer.stop();
contentWindow.visible = false; contentWindow.visible = false;
if (!useSingleWindow && !_needsFullscreenMotion) if (!useSingleWindow)
clickCatcher.visible = false; clickCatcher.visible = false;
dialogClosed(); dialogClosed();
Qt.callLater(() => animationsEnabled = true); Qt.callLater(() => animationsEnabled = true);
@@ -141,7 +140,7 @@ Item {
const newScreen = CompositorService.getFocusedScreen(); const newScreen = CompositorService.getFocusedScreen();
if (newScreen) { if (newScreen) {
contentWindow.screen = newScreen; contentWindow.screen = newScreen;
if (!useSingleWindow && !_needsFullscreenMotion) if (!useSingleWindow)
clickCatcher.screen = newScreen; clickCatcher.screen = newScreen;
} }
} }
@@ -154,7 +153,7 @@ Item {
if (shouldBeVisible) if (shouldBeVisible)
return; return;
contentWindow.visible = false; contentWindow.visible = false;
if (!useSingleWindow && !_needsFullscreenMotion) if (!useSingleWindow)
clickCatcher.visible = false; clickCatcher.visible = false;
dialogClosed(); dialogClosed();
} }
@@ -164,8 +163,8 @@ Item {
readonly property real shadowFallbackOffset: 6 readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0 readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: { readonly property real shadowMotionPadding: {
if (_needsFullscreenMotion) if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 && Theme.isDirectionalEffect)
return 0; return 0; // Wayland native overlap mask
if (animationType === "slide") if (animationType === "slide")
return 30; return 30;
if (Theme.isDirectionalEffect) if (Theme.isDirectionalEffect)
@@ -233,9 +232,26 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.closeOnBackgroundClick && root.shouldBeVisible enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: root.backgroundClicked() onClicked: root.backgroundClicked()
} }
Rectangle {
anchors.fill: parent
z: -1
color: "black"
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: opacity > 0
Behavior on opacity {
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
NumberAnimation {
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
} }
PanelWindow { PanelWindow {
@@ -274,12 +290,12 @@ Item {
anchors { anchors {
left: true left: true
top: true top: true
right: root.useSingleWindow || root._needsFullscreenMotion right: root.useSingleWindow
bottom: root.useSingleWindow || root._needsFullscreenMotion bottom: root.useSingleWindow
} }
readonly property real actualMarginLeft: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr)) readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
readonly property real actualMarginTop: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr)) readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
WlrLayershell.margins { WlrLayershell.margins {
left: actualMarginLeft left: actualMarginLeft
@@ -288,8 +304,8 @@ Item {
bottom: 0 bottom: 0
} }
implicitWidth: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : root.alignedWidth + (shadowBuffer * 2) implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : root.alignedHeight + (shadowBuffer * 2) implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
@@ -304,7 +320,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: (root.useSingleWindow || root._needsFullscreenMotion) && root.closeOnBackgroundClick && root.shouldBeVisible enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
z: -2 z: -2
onClicked: root.backgroundClicked() onClicked: root.backgroundClicked()
} }
@@ -313,13 +329,14 @@ Item {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
color: "black" color: "black"
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.useBackground visible: opacity > 0
Behavior on opacity { Behavior on opacity {
enabled: root.animationsEnabled && !Theme.isDirectionalEffect enabled: root.animationsEnabled && !Theme.isDirectionalEffect
DankAnim { NumberAnimation {
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
@@ -335,7 +352,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: (root.useSingleWindow || root._needsFullscreenMotion) && root.shouldBeVisible enabled: root.useSingleWindow && root.shouldBeVisible
hoverEnabled: false hoverEnabled: false
acceptedButtons: Qt.AllButtons acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true onPressed: mouse.accepted = true
@@ -355,6 +372,8 @@ Item {
readonly property real customDistTop: customAnchorY readonly property real customDistTop: customAnchorY
readonly property real customDistBottom: root.screenHeight - customAnchorY readonly property real customDistBottom: root.screenHeight - customAnchorY
readonly property real offsetX: { readonly property real offsetX: {
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0;
if (slide && !directionalEffect && !depthEffect) if (slide && !directionalEffect && !depthEffect)
return 15; return 15;
if (directionalEffect) { if (directionalEffect) {
@@ -388,6 +407,8 @@ Item {
return 0; return 0;
} }
readonly property real offsetY: { readonly property real offsetY: {
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0;
if (slide && !directionalEffect && !depthEffect) if (slide && !directionalEffect && !depthEffect)
return -30; return -30;
if (directionalEffect) { if (directionalEffect) {
@@ -424,28 +445,33 @@ Item {
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY
property real scaleValue: root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed
Behavior on animX { Behavior on animX {
enabled: root.animationsEnabled enabled: root.animationsEnabled
DankAnim { NumberAnimation {
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible) duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
Behavior on animY { Behavior on animY {
enabled: root.animationsEnabled enabled: root.animationsEnabled
DankAnim { NumberAnimation {
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible) duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
Behavior on scaleValue { Behavior on scaleValue {
enabled: root.animationsEnabled enabled: root.animationsEnabled
DankAnim { NumberAnimation {
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible) duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }

View File

@@ -356,6 +356,13 @@ Item {
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.margins {
top: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
bottom: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
left: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
right: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
}
anchors { anchors {
top: true top: true
bottom: true bottom: true
@@ -455,26 +462,49 @@ Item {
width: root.alignedWidth width: root.alignedWidth
height: root.alignedHeight height: root.alignedHeight
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
readonly property bool dockTop: dockEdge === 0
readonly property bool dockBottom: dockEdge === 1
readonly property bool dockLeft: dockEdge === 2
readonly property bool dockRight: dockEdge === 3
readonly property real dockThickness: typeof SettingsData !== "undefined" && SettingsData.showDock ? Theme.px(SettingsData.dockIconSize + (SettingsData.dockMargin * 2) + SettingsData.dockSpacing + 8, root.dpr) : Theme.px(60, root.dpr)
readonly property bool directionalEffect: Theme.isDirectionalEffect readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect readonly property bool depthEffect: Theme.isDepthEffect
readonly property real collapsedMotionX: depthEffect ? Theme.effectAnimOffset * 0.25 : 0 readonly property real collapsedMotionX: {
if (directionalEffect) {
if (dockLeft)
return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset);
if (dockRight)
return root.screenWidth - root._ccX + Theme.effectAnimOffset;
}
if (depthEffect)
return Theme.effectAnimOffset * 0.25;
return 0;
}
readonly property real collapsedMotionY: { readonly property real collapsedMotionY: {
if (directionalEffect) if (directionalEffect) {
return Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1); if (dockTop)
return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset);
if (dockBottom)
return root.screenHeight - root._ccY + root.shadowPad + Theme.effectAnimOffset;
return 0;
}
if (depthEffect) if (depthEffect)
return -Math.max(Theme.effectAnimOffset * 0.85, 34); return -Math.max(Theme.effectAnimOffset * 0.85, 34);
return 0; return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
} }
// animX/animY are Behavior-animated — DankPopout pattern // animX/animY are Behavior-animated — DankPopout pattern
property real animX: 0 property real animX: 0
property real animY: 0 property real animY: 0
property real scaleValue: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed property real scaleValue: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed)
Component.onCompleted: { Component.onCompleted: {
animX = Theme.snap(root._motionActive ? 0 : collapsedMotionX, root.dpr); animX = Theme.snap(root._motionActive ? 0 : collapsedMotionX, root.dpr);
animY = Theme.snap(root._motionActive ? 0 : collapsedMotionY, root.dpr); animY = Theme.snap(root._motionActive ? 0 : collapsedMotionY, root.dpr);
scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed); scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed));
} }
Connections { Connections {
@@ -482,7 +512,7 @@ Item {
function on_MotionActiveChanged() { function on_MotionActiveChanged() {
contentContainer.animX = Theme.snap(root._motionActive ? 0 : root._frozenMotionX, root.dpr); contentContainer.animX = Theme.snap(root._motionActive ? 0 : root._frozenMotionX, root.dpr);
contentContainer.animY = Theme.snap(root._motionActive ? 0 : root._frozenMotionY, root.dpr); contentContainer.animY = Theme.snap(root._motionActive ? 0 : root._frozenMotionY, root.dpr);
contentContainer.scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed); contentContainer.scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed));
} }
} }
@@ -503,83 +533,105 @@ Item {
} }
Behavior on scaleValue { Behavior on scaleValue {
enabled: root.animationsEnabled && !Theme.isDirectionalEffect enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2))
DankAnim { DankAnim {
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive) duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
} }
} }
// Shadow mirrors contentWrapper position/scale/opacity
ElevationShadow {
id: launcherShadowLayer
width: parent.width
height: parent.height
opacity: contentWrapper.opacity
scale: contentWrapper.scale
x: contentWrapper.x
y: contentWrapper.y
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetColor: root.backgroundColor
borderColor: root.borderColor
borderWidth: root.borderWidth
targetRadius: root.cornerRadius
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
// contentWrapper moves inside static contentContainer — DankPopout pattern
Item { Item {
id: contentWrapper id: directionalClipMask
width: parent.width readonly property bool shouldClip: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0
height: parent.height readonly property real clipOversize: 2000
opacity: Theme.isDirectionalEffect ? 1 : (launcherMotionVisible ? 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)
Behavior on opacity { clip: shouldClip
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
DankAnim { x: shouldClip ? (contentContainer.dockRight ? -clipOversize : (contentContainer.dockLeft ? contentContainer.dockThickness - root._ccX : -clipOversize)) : 0
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale) y: shouldClip ? (contentContainer.dockBottom ? -clipOversize : (contentContainer.dockTop ? contentContainer.dockThickness - root._ccY : -clipOversize)) : 0
easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
width: shouldClip ? parent.width + clipOversize + (contentContainer.dockRight ? (root.screenWidth - contentContainer.dockThickness - root._ccX - parent.width) : (contentContainer.dockLeft ? clipOversize : clipOversize)) : parent.width
height: shouldClip ? parent.height + clipOversize + (contentContainer.dockBottom ? (root.screenHeight - contentContainer.dockThickness - root._ccY - parent.height) : (contentContainer.dockTop ? clipOversize : clipOversize)) : parent.height
Item {
id: aligner
x: directionalClipMask.x !== 0 ? -directionalClipMask.x : 0
y: directionalClipMask.y !== 0 ? -directionalClipMask.y : 0
width: contentContainer.width
height: contentContainer.height
// Shadow mirrors contentWrapper position/scale/opacity
ElevationShadow {
id: launcherShadowLayer
width: parent.width
height: parent.height
opacity: contentWrapper.opacity
scale: contentWrapper.scale
x: contentWrapper.x
y: contentWrapper.y
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetColor: root.backgroundColor
borderColor: root.borderColor
borderWidth: root.borderWidth
targetRadius: root.cornerRadius
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
} }
}
MouseArea { // contentWrapper moves inside static contentContainer — DankPopout pattern
anchors.fill: parent Item {
onPressed: mouse => mouse.accepted = true id: contentWrapper
} width: parent.width
height: parent.height
opacity: Theme.isDirectionalEffect ? 1 : (launcherMotionVisible ? 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)
FocusScope { Behavior on opacity {
anchors.fill: parent enabled: root.animationsEnabled && !Theme.isDirectionalEffect
focus: keyboardActive DankAnim {
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale)
Loader { easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
id: launcherContentLoader
anchors.fill: parent
active: !root.unloadContentOnClose || root.spotlightOpen || root.isClosing || root.contentVisible || root._pendingInitialize
asynchronous: false
sourceComponent: LauncherContent {
focus: true
parentModal: root
}
onLoaded: {
if (root._pendingInitialize) {
root._initializeAndShow(root._pendingQuery, root._pendingMode);
root._pendingInitialize = false;
} }
} }
}
Keys.onEscapePressed: event => { MouseArea {
root.hide(); anchors.fill: parent
event.accepted = true; onPressed: mouse => mouse.accepted = true
} }
}
} FocusScope {
} anchors.fill: parent
} focus: keyboardActive
Loader {
id: launcherContentLoader
anchors.fill: parent
active: !root.unloadContentOnClose || root.spotlightOpen || root.isClosing || root.contentVisible || root._pendingInitialize
asynchronous: false
sourceComponent: LauncherContent {
focus: true
parentModal: root
}
onLoaded: {
if (root._pendingInitialize) {
root._initializeAndShow(root._pendingQuery, root._pendingMode);
root._pendingInitialize = false;
}
}
}
Keys.onEscapePressed: event => {
root.hide();
event.accepted = true;
}
}
} // contentWrapper
} // aligner
} // directionalClipMask
} // contentContainer
} // PanelWindow
} }

View File

@@ -191,6 +191,42 @@ Item {
} }
} }
} }
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: SettingsData.motionEffect === 1
}
SettingsDropdownRow {
visible: SettingsData.motionEffect === 1
tab: "typography"
tags: ["animation", "directional", "behavior", "overlap", "sticky", "roll"]
settingKey: "directionalAnimationMode"
text: I18n.tr("Directional Behavior")
description: I18n.tr("How the popout emerges from the DankBar")
options: [I18n.tr("Overlap"), I18n.tr("Slide"), I18n.tr("Roll")]
currentValue: {
switch (SettingsData.directionalAnimationMode) {
case 1:
return I18n.tr("Slide");
case 2:
return I18n.tr("Roll");
default:
return I18n.tr("Overlap");
}
}
onValueChanged: value => {
if (value === I18n.tr("Slide"))
SettingsData.set("directionalAnimationMode", 1);
else if (value === I18n.tr("Roll"))
SettingsData.set("directionalAnimationMode", 2);
else
SettingsData.set("directionalAnimationMode", 0);
}
}
} }
SettingsCard { SettingsCard {

View File

@@ -259,8 +259,11 @@ Item {
readonly property real shadowFallbackOffset: 6 readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0 readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: { readonly property real shadowMotionPadding: {
if (Theme.isDirectionalEffect) if (Theme.isDirectionalEffect) {
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode !== 0)
return 16; // Slide Behind and Roll Out do not add animationOffset, enabling strict Wayland clipping.
return Math.max(0, animationOffset) + 16; return Math.max(0, animationOffset) + 16;
}
if (Theme.isDepthEffect) if (Theme.isDepthEffect)
return Math.max(0, animationOffset) + 8; return Math.max(0, animationOffset) + 8;
return Math.max(0, animationOffset); return Math.max(0, animationOffset);
@@ -469,17 +472,6 @@ Item {
visible: false visible: false
color: "transparent" 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.namespace: root.layerNamespace
WlrLayershell.layer: { WlrLayershell.layer: {
switch (Quickshell.env("DMS_POPOUT_LAYER")) { switch (Quickshell.env("DMS_POPOUT_LAYER")) {
@@ -534,8 +526,8 @@ Item {
visible: false visible: false
x: contentContainer.x x: contentContainer.x
y: contentContainer.y y: contentContainer.y
width: shouldBeVisible ? root.alignedWidth : 0 width: root.alignedWidth
height: shouldBeVisible ? root.alignedHeight : 0 height: root.alignedHeight
} }
MouseArea { MouseArea {
@@ -572,6 +564,8 @@ Item {
readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0)) readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0))
readonly property real offsetX: { readonly property real offsetX: {
if (directionalEffect) { if (directionalEffect) {
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2)
return 0;
if (barLeft) if (barLeft)
return -directionalTravelX; return -directionalTravelX;
if (barRight) if (barRight)
@@ -593,6 +587,8 @@ Item {
} }
readonly property real offsetY: { readonly property real offsetY: {
if (directionalEffect) { if (directionalEffect) {
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2)
return 0;
if (barBottom) if (barBottom)
return directionalTravelY; return directionalTravelY;
if (barTop) if (barTop)
@@ -615,12 +611,14 @@ Item {
property real animX: 0 property real animX: 0
property real animY: 0 property real animY: 0
property real scaleValue: root.animationScaleCollapsed
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
property real scaleValue: computedScaleCollapsed
Component.onCompleted: { Component.onCompleted: {
animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr); animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr);
animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr); animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr);
scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed; scaleValue = root.shouldBeVisible ? 1.0 : computedScaleCollapsed;
} }
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr) onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
@@ -631,7 +629,7 @@ Item {
function onShouldBeVisibleChanged() { function onShouldBeVisibleChanged() {
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr); contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr); contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr);
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed; contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : contentContainer.computedScaleCollapsed;
} }
} }
@@ -662,68 +660,100 @@ Item {
} }
} }
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)
}
Item { Item {
id: contentWrapper id: directionalClipMask
width: parent.width
height: parent.height
opacity: Theme.isDirectionalEffect ? 1 : (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 readonly property bool shouldClip: typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 && Theme.isDirectionalEffect
layer.smooth: false readonly property real clipOversize: 1000
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
Behavior on opacity { clip: shouldClip
enabled: !Theme.isDirectionalEffect
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Loader { // Bound the clipping strictly to the bar side, allowing massive overflow on the other 3 sides for shadows
id: contentLoader x: shouldClip ? (contentContainer.barRight ? -clipOversize : (contentContainer.barLeft ? 0 : -clipOversize)) : 0
anchors.fill: parent y: shouldClip ? (contentContainer.barBottom ? -clipOversize : (contentContainer.barTop ? 0 : -clipOversize)) : 0
active: root._primeContent || shouldBeVisible || contentWindow.visible
asynchronous: false
}
}
Rectangle { width: shouldClip ? parent.width + clipOversize + (contentContainer.barLeft || contentContainer.barRight ? 0 : clipOversize) : parent.width
width: parent.width height: shouldClip ? parent.height + clipOversize + (contentContainer.barTop || contentContainer.barBottom ? 0 : clipOversize) : parent.height
height: parent.height
x: contentWrapper.x Item {
y: contentWrapper.y id: aligner
opacity: contentWrapper.opacity readonly property real baseWidth: contentContainer.width
scale: contentWrapper.scale readonly property real baseHeight: contentContainer.height
visible: contentWrapper.visible readonly property bool isRollOut: typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect
radius: Theme.cornerRadius
color: "transparent" x: (directionalClipMask.x !== 0 ? -directionalClipMask.x : 0) + (isRollOut && contentContainer.barRight ? baseWidth * (1 - contentContainer.scaleValue) : 0)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium y: (directionalClipMask.y !== 0 ? -directionalClipMask.y : 0) + (isRollOut && contentContainer.barBottom ? baseHeight * (1 - contentContainer.scaleValue) : 0)
border.width: BlurService.borderWidth width: isRollOut && (contentContainer.barLeft || contentContainer.barRight) ? Math.max(0, baseWidth * contentContainer.scaleValue) : baseWidth
z: 100 height: isRollOut && (contentContainer.barTop || contentContainer.barBottom) ? Math.max(0, baseHeight * contentContainer.scaleValue) : baseHeight
}
} clip: isRollOut
Item {
id: unrollCounteract
x: aligner.isRollOut && contentContainer.barRight ? -(aligner.baseWidth * (1 - contentContainer.scaleValue)) : 0
y: aligner.isRollOut && contentContainer.barBottom ? -(aligner.baseHeight * (1 - contentContainer.scaleValue)) : 0
width: aligner.baseWidth
height: aligner.baseHeight
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)
}
Item {
id: contentWrapper
width: parent.width
height: parent.height
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
visible: opacity > 0
scale: aligner.isRollOut ? 1.0 : contentContainer.scaleValue
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - scale) * 0.5, root.dpr)
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - scale) * 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 {
enabled: !Theme.isDirectionalEffect
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Theme.outlineMedium
border.width: 0
}
Loader {
id: contentLoader
anchors.fill: parent
active: root._primeContent || shouldBeVisible || contentWindow.visible
asynchronous: false
}
} // closes contentWrapper
} // closes unrollCounteract
} // closes aligner
} // closes directionalClipMask
} // closes contentContainer
Item { Item {
id: focusHelper id: focusHelper