mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-13 17:22:08 -04:00
Initial staging for Animation & Motion effects
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.9.0
|
rev: v2.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-fmt
|
- id: golangci-lint-fmt
|
||||||
require_serial: true
|
require_serial: true
|
||||||
|
|||||||
147
quickshell/Common/AnimVariants.qml
Normal file
147
quickshell/Common/AnimVariants.qml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
// AnimVariants — Central tuning for animation and Motion Effects variants
|
||||||
|
// (Material/Fluent/Dynamic) (Standard/Directional/Depth)
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property list<real> variantEnterCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.expressiveDefaultSpatial;
|
||||||
|
switch (SettingsData.animationVariant) {
|
||||||
|
case 1: return Anims.standardDecel;
|
||||||
|
case 2: return Anims.expressiveFastSpatial;
|
||||||
|
default: return Anims.expressiveDefaultSpatial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<real> variantExitCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.emphasized;
|
||||||
|
switch (SettingsData.animationVariant) {
|
||||||
|
case 1: return Anims.standard;
|
||||||
|
case 2: return Anims.emphasized;
|
||||||
|
default: return Anims.emphasized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal-specific entry curve
|
||||||
|
readonly property list<real> variantModalEnterCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.expressiveDefaultSpatial;
|
||||||
|
if (isDirectionalEffect) {
|
||||||
|
if (SettingsData.animationVariant === 1)
|
||||||
|
return Anims.standardDecel;
|
||||||
|
if (SettingsData.animationVariant === 2)
|
||||||
|
return Anims.expressiveFastSpatial;
|
||||||
|
}
|
||||||
|
return variantEnterCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<real> variantModalExitCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.emphasized;
|
||||||
|
if (isDirectionalEffect) {
|
||||||
|
if (SettingsData.animationVariant === 1)
|
||||||
|
return Anims.emphasizedAccel;
|
||||||
|
if (SettingsData.animationVariant === 2)
|
||||||
|
return Anims.emphasizedAccel;
|
||||||
|
}
|
||||||
|
return variantExitCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popout-specific entry curve
|
||||||
|
readonly property list<real> variantPopoutEnterCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.expressiveDefaultSpatial;
|
||||||
|
if (isDirectionalEffect) {
|
||||||
|
if (SettingsData.animationVariant === 1)
|
||||||
|
return Anims.standardDecel;
|
||||||
|
if (SettingsData.animationVariant === 2)
|
||||||
|
return Anims.standardDecel;
|
||||||
|
return Anims.standardDecel;
|
||||||
|
}
|
||||||
|
return variantEnterCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property list<real> variantPopoutExitCurve: {
|
||||||
|
if (typeof SettingsData === "undefined")
|
||||||
|
return Anims.emphasized;
|
||||||
|
if (isDirectionalEffect) {
|
||||||
|
if (SettingsData.animationVariant === 1)
|
||||||
|
return Anims.emphasizedAccel;
|
||||||
|
if (SettingsData.animationVariant === 2)
|
||||||
|
return Anims.emphasizedAccel;
|
||||||
|
}
|
||||||
|
return variantExitCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real variantEnterDurationFactor: {
|
||||||
|
if (typeof SettingsData === "undefined") return 1.0;
|
||||||
|
switch (SettingsData.animationVariant) {
|
||||||
|
case 1: return 0.9;
|
||||||
|
case 2: return 1.08;
|
||||||
|
default: return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real variantExitDurationFactor: {
|
||||||
|
if (typeof SettingsData === "undefined") return 1.0;
|
||||||
|
switch (SettingsData.animationVariant) {
|
||||||
|
case 1: return 0.85;
|
||||||
|
case 2: return 0.92;
|
||||||
|
default: return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fluent: opacity at ~55% of duration; Material/Dynamic: 1:1 with position
|
||||||
|
readonly property real variantOpacityDurationScale: {
|
||||||
|
if (typeof SettingsData === "undefined") return 1.0;
|
||||||
|
return SettingsData.animationVariant === 1 ? 0.55 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function variantDuration(baseDuration, entering) {
|
||||||
|
const factor = entering ? variantEnterDurationFactor : variantExitDurationFactor;
|
||||||
|
return Math.max(0, Math.round(baseDuration * factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
function variantExitCleanupPadding() {
|
||||||
|
if (typeof SettingsData === "undefined") return 50;
|
||||||
|
switch (SettingsData.motionEffect) {
|
||||||
|
case 1: return 8;
|
||||||
|
case 2: return 24;
|
||||||
|
default: return 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function variantCloseInterval(baseDuration) {
|
||||||
|
return variantDuration(baseDuration, false) + variantExitCleanupPadding();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool isDirectionalEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 1
|
||||||
|
readonly property bool isDepthEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 2
|
||||||
|
|
||||||
|
readonly property real effectScaleCollapsed: {
|
||||||
|
if (typeof SettingsData === "undefined") return 0.96;
|
||||||
|
switch (SettingsData.motionEffect) {
|
||||||
|
case 1: return 1.0;
|
||||||
|
case 2: return 0.88;
|
||||||
|
default: return 0.96;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real effectAnimOffset: {
|
||||||
|
if (typeof SettingsData === "undefined") return 16;
|
||||||
|
switch (SettingsData.motionEffect) {
|
||||||
|
case 1: return 144;
|
||||||
|
case 2: return 56;
|
||||||
|
default: return 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,4 +22,9 @@ Singleton {
|
|||||||
readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00]
|
readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||||
readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00]
|
readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||||
readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
|
readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
|
||||||
|
|
||||||
|
// Used by AnimVariants for variant/effect logic
|
||||||
|
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 expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ Singleton {
|
|||||||
Custom
|
Custom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AnimationVariant {
|
||||||
|
Material,
|
||||||
|
Fluent,
|
||||||
|
Dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnimationEffect {
|
||||||
|
Standard, // 0 — M3: scale-in, rises from below
|
||||||
|
Directional, // 1 — pure large slide, no scale
|
||||||
|
Depth // 2 — medium slide with deep depth scale pop
|
||||||
|
}
|
||||||
|
|
||||||
enum SuspendBehavior {
|
enum SuspendBehavior {
|
||||||
Suspend,
|
Suspend,
|
||||||
Hibernate,
|
Hibernate,
|
||||||
@@ -166,6 +178,10 @@ Singleton {
|
|||||||
property int modalCustomAnimationDuration: 150
|
property int modalCustomAnimationDuration: 150
|
||||||
property bool enableRippleEffects: true
|
property bool enableRippleEffects: true
|
||||||
onEnableRippleEffectsChanged: saveSettings()
|
onEnableRippleEffectsChanged: saveSettings()
|
||||||
|
property int animationVariant: SettingsData.AnimationVariant.Material
|
||||||
|
onAnimationVariantChanged: saveSettings()
|
||||||
|
property int motionEffect: SettingsData.AnimationEffect.Standard
|
||||||
|
onMotionEffectChanged: saveSettings()
|
||||||
property bool m3ElevationEnabled: true
|
property bool m3ElevationEnabled: true
|
||||||
onM3ElevationEnabledChanged: saveSettings()
|
onM3ElevationEnabledChanged: saveSettings()
|
||||||
property int m3ElevationIntensity: 12
|
property int m3ElevationIntensity: 12
|
||||||
|
|||||||
@@ -960,6 +960,24 @@ Singleton {
|
|||||||
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
|
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delegates to AnimVariants.qml for curves, timing, scale, and offsets.
|
||||||
|
readonly property list<real> variantEnterCurve: AnimVariants.variantEnterCurve
|
||||||
|
readonly property list<real> variantExitCurve: AnimVariants.variantExitCurve
|
||||||
|
readonly property list<real> variantModalEnterCurve: AnimVariants.variantModalEnterCurve
|
||||||
|
readonly property list<real> variantModalExitCurve: AnimVariants.variantModalExitCurve
|
||||||
|
readonly property list<real> variantPopoutEnterCurve: AnimVariants.variantPopoutEnterCurve
|
||||||
|
readonly property list<real> variantPopoutExitCurve: AnimVariants.variantPopoutExitCurve
|
||||||
|
readonly property real variantEnterDurationFactor: AnimVariants.variantEnterDurationFactor
|
||||||
|
readonly property real variantExitDurationFactor: AnimVariants.variantExitDurationFactor
|
||||||
|
readonly property real variantOpacityDurationScale: AnimVariants.variantOpacityDurationScale
|
||||||
|
readonly property bool isDirectionalEffect: AnimVariants.isDirectionalEffect
|
||||||
|
readonly property bool isDepthEffect: AnimVariants.isDepthEffect
|
||||||
|
readonly property real effectScaleCollapsed: AnimVariants.effectScaleCollapsed
|
||||||
|
readonly property real effectAnimOffset: AnimVariants.effectAnimOffset
|
||||||
|
function variantDuration(baseDuration, entering) { return AnimVariants.variantDuration(baseDuration, entering); }
|
||||||
|
function variantExitCleanupPadding() { return AnimVariants.variantExitCleanupPadding(); }
|
||||||
|
function variantCloseInterval(baseDuration) { return AnimVariants.variantCloseInterval(baseDuration); }
|
||||||
|
|
||||||
readonly property var animationPresetDurations: {
|
readonly property var animationPresetDurations: {
|
||||||
"none": 0,
|
"none": 0,
|
||||||
"short": 250,
|
"short": 250,
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ var SPEC = {
|
|||||||
modalAnimationSpeed: { def: 1 },
|
modalAnimationSpeed: { def: 1 },
|
||||||
modalCustomAnimationDuration: { def: 150 },
|
modalCustomAnimationDuration: { def: 150 },
|
||||||
enableRippleEffects: { def: true },
|
enableRippleEffects: { def: true },
|
||||||
|
animationVariant: { def: 0 },
|
||||||
|
motionEffect: { def: 0 },
|
||||||
m3ElevationEnabled: { def: true },
|
m3ElevationEnabled: { def: true },
|
||||||
m3ElevationIntensity: { def: 12 },
|
m3ElevationIntensity: { def: 12 },
|
||||||
m3ElevationOpacity: { def: 30 },
|
m3ElevationOpacity: { def: 30 },
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ Item {
|
|||||||
property bool closeOnBackgroundClick: true
|
property bool closeOnBackgroundClick: true
|
||||||
property string animationType: "scale"
|
property string animationType: "scale"
|
||||||
property int animationDuration: Theme.modalAnimationDuration
|
property int animationDuration: Theme.modalAnimationDuration
|
||||||
property real animationScaleCollapsed: 0.96
|
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||||
property real animationOffset: Theme.spacingL
|
property real animationOffset: Theme.effectAnimOffset
|
||||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
property list<real> animationEnterCurve: Theme.variantModalEnterCurve
|
||||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
property list<real> animationExitCurve: Theme.variantModalExitCurve
|
||||||
property color backgroundColor: Theme.surfaceContainer
|
property color backgroundColor: Theme.surfaceContainer
|
||||||
property color borderColor: Theme.outlineMedium
|
property color borderColor: Theme.outlineMedium
|
||||||
property real borderWidth: 0
|
property real borderWidth: 0
|
||||||
@@ -44,11 +44,14 @@ Item {
|
|||||||
property bool keepPopoutsOpen: false
|
property bool keepPopoutsOpen: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool useOverlayLayer: false
|
property bool useOverlayLayer: false
|
||||||
|
property real frozenMotionOffsetX: 0
|
||||||
|
property real frozenMotionOffsetY: 0
|
||||||
readonly property alias contentWindow: contentWindow
|
readonly property alias contentWindow: contentWindow
|
||||||
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 || useBackground
|
||||||
|
readonly property bool _needsFullscreenMotion: !useSingleWindow && (Theme.isDirectionalEffect || Theme.isDepthEffect)
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
@@ -58,19 +61,34 @@ Item {
|
|||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
|
animationsEnabled = false;
|
||||||
|
frozenMotionOffsetX = modalContainer ? modalContainer.offsetX : 0;
|
||||||
|
frozenMotionOffsetY = modalContainer ? modalContainer.offsetY : animationOffset;
|
||||||
|
|
||||||
const focusedScreen = CompositorService.getFocusedScreen();
|
const focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow && !_needsFullscreenMotion)
|
||||||
clickCatcher.screen = focusedScreen;
|
clickCatcher.screen = focusedScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Theme.isDirectionalEffect) {
|
||||||
|
if (!useSingleWindow && !_needsFullscreenMotion)
|
||||||
|
clickCatcher.visible = true;
|
||||||
|
contentWindow.visible = true;
|
||||||
|
}
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
shouldBeVisible = true;
|
|
||||||
if (!useSingleWindow)
|
Qt.callLater(() => {
|
||||||
clickCatcher.visible = true;
|
animationsEnabled = true;
|
||||||
contentWindow.visible = true;
|
shouldBeVisible = true;
|
||||||
shouldHaveFocus = false;
|
if (!useSingleWindow && !_needsFullscreenMotion && !clickCatcher.visible)
|
||||||
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
clickCatcher.visible = true;
|
||||||
|
if (!contentWindow.visible)
|
||||||
|
contentWindow.visible = true;
|
||||||
|
shouldHaveFocus = false;
|
||||||
|
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
@@ -87,7 +105,7 @@ Item {
|
|||||||
ModalManager.closeModal(root);
|
ModalManager.closeModal(root);
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow && !_needsFullscreenMotion)
|
||||||
clickCatcher.visible = false;
|
clickCatcher.visible = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
Qt.callLater(() => animationsEnabled = true);
|
Qt.callLater(() => animationsEnabled = true);
|
||||||
@@ -123,7 +141,7 @@ Item {
|
|||||||
const newScreen = CompositorService.getFocusedScreen();
|
const newScreen = CompositorService.getFocusedScreen();
|
||||||
if (newScreen) {
|
if (newScreen) {
|
||||||
contentWindow.screen = newScreen;
|
contentWindow.screen = newScreen;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow && !_needsFullscreenMotion)
|
||||||
clickCatcher.screen = newScreen;
|
clickCatcher.screen = newScreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,12 +149,12 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeTimer
|
id: closeTimer
|
||||||
interval: animationDuration + 50
|
interval: Theme.variantCloseInterval(animationDuration)
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (shouldBeVisible)
|
if (shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow && !_needsFullscreenMotion)
|
||||||
clickCatcher.visible = false;
|
clickCatcher.visible = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
}
|
}
|
||||||
@@ -145,7 +163,17 @@ Item {
|
|||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
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: animationType === "slide" ? 30 : Math.max(0, animationOffset)
|
readonly property real shadowMotionPadding: {
|
||||||
|
if (_needsFullscreenMotion)
|
||||||
|
return 0;
|
||||||
|
if (animationType === "slide")
|
||||||
|
return 30;
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
|
||||||
|
return Math.max(0, animationOffset);
|
||||||
|
}
|
||||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
@@ -246,19 +274,22 @@ Item {
|
|||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
right: root.useSingleWindow
|
right: root.useSingleWindow || root._needsFullscreenMotion
|
||||||
bottom: root.useSingleWindow
|
bottom: root.useSingleWindow || root._needsFullscreenMotion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property real actualMarginLeft: (root.useSingleWindow || root._needsFullscreenMotion) ? 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))
|
||||||
|
|
||||||
WlrLayershell.margins {
|
WlrLayershell.margins {
|
||||||
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
left: actualMarginLeft
|
||||||
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
top: actualMarginTop
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
implicitWidth: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
||||||
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
implicitHeight: (root.useSingleWindow || root._needsFullscreenMotion) ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
@@ -273,7 +304,7 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: (root.useSingleWindow || root._needsFullscreenMotion) && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
z: -2
|
z: -2
|
||||||
onClicked: root.backgroundClicked()
|
onClicked: root.backgroundClicked()
|
||||||
}
|
}
|
||||||
@@ -286,9 +317,9 @@ Item {
|
|||||||
visible: root.useBackground
|
visible: root.useBackground
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: root.animationDuration
|
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,15 +327,15 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useSingleWindow ? root.alignedX : shadowBuffer
|
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
||||||
y: root.useSingleWindow ? root.alignedY : shadowBuffer
|
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
||||||
|
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.useSingleWindow && root.shouldBeVisible
|
enabled: (root.useSingleWindow || root._needsFullscreenMotion) && root.shouldBeVisible
|
||||||
hoverEnabled: false
|
hoverEnabled: false
|
||||||
acceptedButtons: Qt.AllButtons
|
acceptedButtons: Qt.AllButtons
|
||||||
onPressed: mouse.accepted = true
|
onPressed: mouse.accepted = true
|
||||||
@@ -313,29 +344,92 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool slide: root.animationType === "slide"
|
readonly property bool slide: root.animationType === "slide"
|
||||||
readonly property real offsetX: slide ? 15 : 0
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
readonly property real offsetY: slide ? -30 : root.animationOffset
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||||
property real animX: 0
|
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||||
property real animY: 0
|
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||||
property real scaleValue: root.animationScaleCollapsed
|
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||||
|
readonly property real customDistLeft: customAnchorX
|
||||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
readonly property real customDistTop: customAnchorY
|
||||||
|
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||||
Connections {
|
readonly property real offsetX: {
|
||||||
target: root
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
function onShouldBeVisibleChanged() {
|
return 15;
|
||||||
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
|
if (directionalEffect) {
|
||||||
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
|
switch (root.positioning) {
|
||||||
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
case "top-right":
|
||||||
|
return 0;
|
||||||
|
case "custom":
|
||||||
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return 0;
|
||||||
|
case "custom":
|
||||||
|
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||||
|
return depthTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
readonly property real offsetY: {
|
||||||
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
|
return -30;
|
||||||
|
if (directionalEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -Math.max(directionalTravel * 0.65, 96);
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -directionalTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return directionalTravel;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
// Default to sliding down from top when centered
|
||||||
|
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
switch (root.positioning) {
|
||||||
|
case "top-right":
|
||||||
|
return -depthTravel * 0.75;
|
||||||
|
case "custom":
|
||||||
|
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||||
|
return -depthTravel;
|
||||||
|
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||||
|
return depthTravel;
|
||||||
|
return depthTravel * 0.45;
|
||||||
|
default:
|
||||||
|
return -depthTravel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root.animationOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
|
||||||
|
property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY
|
||||||
|
property real scaleValue: root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed
|
||||||
|
|
||||||
Behavior on animX {
|
Behavior on animX {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +437,7 @@ Item {
|
|||||||
Behavior on animY {
|
Behavior on animY {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +445,7 @@ Item {
|
|||||||
Behavior on scaleValue {
|
Behavior on scaleValue {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,15 +461,14 @@ Item {
|
|||||||
id: animatedContent
|
id: animatedContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: false
|
clip: false
|
||||||
opacity: root.shouldBeVisible ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
scale: modalContainer.scaleValue
|
scale: modalContainer.scaleValue
|
||||||
x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
|
transformOrigin: Item.Center
|
||||||
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
|
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: animationDuration
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property bool spotlightOpen: false
|
property bool spotlightOpen: false
|
||||||
property bool keyboardActive: false
|
property bool keyboardActive: false
|
||||||
property bool contentVisible: false
|
property bool contentVisible: false
|
||||||
|
readonly property bool launcherMotionVisible: Theme.isDirectionalEffect ? spotlightOpen : _motionActive
|
||||||
property var spotlightContent: launcherContentLoader.item
|
property var spotlightContent: launcherContentLoader.item
|
||||||
property bool openedFromOverview: false
|
property bool openedFromOverview: false
|
||||||
property bool isClosing: false
|
property bool isClosing: false
|
||||||
@@ -23,8 +24,14 @@ Item {
|
|||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
||||||
|
|
||||||
|
// Animation state — matches DankPopout/DankModal pattern
|
||||||
|
property bool animationsEnabled: true
|
||||||
|
property bool _motionActive: false
|
||||||
|
property real _frozenMotionX: 0
|
||||||
|
property real _frozenMotionY: 0
|
||||||
|
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
readonly property var effectiveScreen: launcherWindow.screen
|
readonly property var effectiveScreen: contentWindow.screen
|
||||||
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
||||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||||
@@ -78,6 +85,34 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
||||||
|
|
||||||
|
// Shadow padding for the content window (render padding only, no motion padding)
|
||||||
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
|
readonly property real shadowFallbackOffset: 6
|
||||||
|
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||||
|
readonly property real shadowPad: Theme.snap(shadowRenderPadding, dpr)
|
||||||
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
|
readonly property real alignedX: Theme.snap(modalX, dpr)
|
||||||
|
readonly property real alignedY: Theme.snap(modalY, dpr)
|
||||||
|
|
||||||
|
// For directional/depth: window extends from screen top (content slides within)
|
||||||
|
// For standard: small window tightly around the modal + shadow padding
|
||||||
|
readonly property bool _needsExtendedWindow: Theme.isDirectionalEffect || Theme.isDepthEffect
|
||||||
|
// Content window geometry
|
||||||
|
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
|
||||||
|
readonly property real _cwMarginTop: _needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr)
|
||||||
|
readonly property real _cwWidth: alignedWidth + shadowPad * 2
|
||||||
|
readonly property real _cwHeight: {
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return screenHeight + shadowPad;
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return alignedY + alignedHeight + shadowPad;
|
||||||
|
return alignedHeight + shadowPad * 2;
|
||||||
|
}
|
||||||
|
// Where the content container sits inside the content window
|
||||||
|
readonly property real _ccX: shadowPad
|
||||||
|
readonly property real _ccY: _needsExtendedWindow ? alignedY : shadowPad
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
function _ensureContentLoadedAndInitialize(query, mode) {
|
function _ensureContentLoadedAndInitialize(query, mode) {
|
||||||
@@ -97,7 +132,8 @@ Item {
|
|||||||
if (!spotlightContent)
|
if (!spotlightContent)
|
||||||
return;
|
return;
|
||||||
contentVisible = true;
|
contentVisible = true;
|
||||||
spotlightContent.searchField.forceActiveFocus();
|
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
||||||
|
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
||||||
|
|
||||||
if (spotlightContent.searchField) {
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
@@ -130,40 +166,59 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function _openCommon(query, mode) {
|
||||||
closeCleanupTimer.stop();
|
closeCleanupTimer.stop();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
// Disable animations so the snap is instant
|
||||||
if (focusedScreen)
|
animationsEnabled = false;
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
// Freeze the collapsed offsets (they depend on height which could change)
|
||||||
keyboardActive = true;
|
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
||||||
|
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
||||||
|
|
||||||
|
var focusedScreen = CompositorService.getFocusedScreen();
|
||||||
|
if (focusedScreen) {
|
||||||
|
backgroundWindow.screen = focusedScreen;
|
||||||
|
contentWindow.screen = focusedScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
|
||||||
|
_motionActive = false;
|
||||||
|
|
||||||
|
// Make windows visible but do NOT request keyboard focus yet
|
||||||
ModalManager.openModal(root);
|
ModalManager.openModal(root);
|
||||||
|
spotlightOpen = true;
|
||||||
|
backgroundWindow.visible = true;
|
||||||
|
contentWindow.visible = true;
|
||||||
if (useHyprlandFocusGrab)
|
if (useHyprlandFocusGrab)
|
||||||
focusGrab.active = true;
|
focusGrab.active = true;
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize("", "");
|
// Load content and initialize (but no forceActiveFocus — that's deferred)
|
||||||
|
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||||
|
|
||||||
|
// Frame 1: enable animations and trigger enter motion
|
||||||
|
Qt.callLater(() => {
|
||||||
|
root.animationsEnabled = true;
|
||||||
|
root._motionActive = true;
|
||||||
|
|
||||||
|
// Frame 2: request keyboard focus + activate search field
|
||||||
|
// Double-deferred to avoid compositor IPC competing with animation frames
|
||||||
|
Qt.callLater(() => {
|
||||||
|
root.keyboardActive = true;
|
||||||
|
if (root.spotlightContent && root.spotlightContent.searchField)
|
||||||
|
root.spotlightContent.searchField.forceActiveFocus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
_openCommon("", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithQuery(query) {
|
function showWithQuery(query) {
|
||||||
closeCleanupTimer.stop();
|
_openCommon(query, "");
|
||||||
isClosing = false;
|
|
||||||
openedFromOverview = false;
|
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
|
||||||
if (focusedScreen)
|
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
|
||||||
keyboardActive = true;
|
|
||||||
ModalManager.openModal(root);
|
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize(query, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -171,13 +226,17 @@ Item {
|
|||||||
return;
|
return;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
contentVisible = false;
|
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||||
|
if (!Theme.isDirectionalEffect)
|
||||||
|
contentVisible = false;
|
||||||
|
|
||||||
|
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
||||||
|
_motionActive = false;
|
||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
focusGrab.active = false;
|
focusGrab.active = false;
|
||||||
ModalManager.closeModal(root);
|
ModalManager.closeModal(root);
|
||||||
|
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,21 +245,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showWithMode(mode) {
|
function showWithMode(mode) {
|
||||||
closeCleanupTimer.stop();
|
_openCommon("", mode);
|
||||||
isClosing = false;
|
|
||||||
openedFromOverview = false;
|
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
|
||||||
if (focusedScreen)
|
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
|
||||||
keyboardActive = true;
|
|
||||||
ModalManager.openModal(root);
|
|
||||||
if (useHyprlandFocusGrab)
|
|
||||||
focusGrab.active = true;
|
|
||||||
|
|
||||||
_ensureContentLoadedAndInitialize("", mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWithMode(mode) {
|
function toggleWithMode(mode) {
|
||||||
@@ -221,10 +266,13 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeCleanupTimer
|
id: closeCleanupTimer
|
||||||
interval: Theme.modalAnimationDuration + 50
|
interval: Theme.variantCloseInterval(Theme.modalAnimationDuration)
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
|
contentVisible = false;
|
||||||
|
contentWindow.visible = false;
|
||||||
|
backgroundWindow.visible = false;
|
||||||
if (root.unloadContentOnClose)
|
if (root.unloadContentOnClose)
|
||||||
launcherContentLoader.active = false;
|
launcherContentLoader.active = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
@@ -242,7 +290,7 @@ Item {
|
|||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [launcherWindow]
|
windows: [contentWindow]
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
@@ -267,7 +315,7 @@ Item {
|
|||||||
if (Quickshell.screens.length === 0)
|
if (Quickshell.screens.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const screen = launcherWindow.screen;
|
const screen = contentWindow.screen;
|
||||||
const screenName = screen?.name;
|
const screenName = screen?.name;
|
||||||
|
|
||||||
let needsReset = !screen || !screenName;
|
let needsReset = !screen || !screenName;
|
||||||
@@ -289,35 +337,24 @@ Item {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
root._windowEnabled = false;
|
root._windowEnabled = false;
|
||||||
launcherWindow.screen = newScreen;
|
backgroundWindow.screen = newScreen;
|
||||||
|
contentWindow.screen = newScreen;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root._windowEnabled = true;
|
root._windowEnabled = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Background window: fullscreen, handles darkening + click-to-dismiss ──
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: launcherWindow
|
id: backgroundWindow
|
||||||
visible: root._windowEnabled && (spotlightOpen || isClosing)
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusionMode: ExclusionMode.Ignore
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight:bg"
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
WlrLayershell.exclusiveZone: -1
|
||||||
case "bottom":
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
|
||||||
return WlrLayershell.Top;
|
|
||||||
case "background":
|
|
||||||
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
|
||||||
return WlrLayershell.Top;
|
|
||||||
case "overlay":
|
|
||||||
return WlrLayershell.Overlay;
|
|
||||||
default:
|
|
||||||
return WlrLayershell.Top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
@@ -327,11 +364,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: spotlightOpen ? fullScreenMask : null
|
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: fullScreenMask
|
id: bgFullScreenMask
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,13 +376,14 @@ Item {
|
|||||||
id: backgroundDarken
|
id: backgroundDarken
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
opacity: launcherMotionVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||||
visible: contentVisible || opacity > 0
|
visible: launcherMotionVisible || opacity > 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: Theme.modalAnimationDuration
|
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,49 +391,136 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: spotlightOpen
|
enabled: spotlightOpen
|
||||||
onClicked: mouse => {
|
onClicked: root.hide()
|
||||||
var contentX = modalContainer.x;
|
}
|
||||||
var contentY = modalContainer.y;
|
}
|
||||||
var contentW = modalContainer.width;
|
|
||||||
var contentH = modalContainer.height;
|
|
||||||
|
|
||||||
if (mouse.x < contentX || mouse.x > contentX + contentW || mouse.y < contentY || mouse.y > contentY + contentH) {
|
// ── Content window: SMALL, positioned with margins — only renders the modal area ──
|
||||||
root.hide();
|
PanelWindow {
|
||||||
}
|
id: contentWindow
|
||||||
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "dms:spotlight"
|
||||||
|
WlrLayershell.layer: {
|
||||||
|
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||||
|
case "bottom":
|
||||||
|
console.error("DankLauncherV2Modal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||||
|
return WlrLayershell.Top;
|
||||||
|
case "background":
|
||||||
|
console.error("DankLauncherV2Modal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||||
|
return WlrLayershell.Top;
|
||||||
|
case "overlay":
|
||||||
|
return WlrLayershell.Overlay;
|
||||||
|
default:
|
||||||
|
return WlrLayershell.Top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
WlrLayershell.exclusiveZone: -1
|
||||||
|
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: true
|
||||||
|
top: true
|
||||||
|
}
|
||||||
|
|
||||||
|
WlrLayershell.margins {
|
||||||
|
left: root._cwMarginLeft
|
||||||
|
top: root._cwMarginTop
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: root._cwWidth
|
||||||
|
implicitHeight: root._cwHeight
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: contentInputMask
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: contentInputMask
|
||||||
x: root.modalX
|
visible: false
|
||||||
y: root.modalY
|
x: contentContainer.x + contentWrapper.x
|
||||||
width: root.modalWidth
|
y: contentContainer.y + contentWrapper.y
|
||||||
height: root.modalHeight
|
width: root.alignedWidth
|
||||||
visible: contentVisible || opacity > 0
|
height: root.alignedHeight
|
||||||
|
}
|
||||||
|
|
||||||
opacity: contentVisible ? 1 : 0
|
Item {
|
||||||
scale: contentVisible ? 1 : 0.96
|
id: contentContainer
|
||||||
transformOrigin: Item.Center
|
|
||||||
|
|
||||||
Behavior on opacity {
|
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
|
||||||
DankAnim {
|
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
|
||||||
duration: Theme.modalAnimationDuration
|
x: root._ccX
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
y: root._ccY
|
||||||
|
width: root.alignedWidth
|
||||||
|
height: root.alignedHeight
|
||||||
|
|
||||||
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real collapsedMotionX: depthEffect ? Theme.effectAnimOffset * 0.25 : 0
|
||||||
|
readonly property real collapsedMotionY: {
|
||||||
|
if (directionalEffect)
|
||||||
|
return Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1);
|
||||||
|
if (depthEffect)
|
||||||
|
return -Math.max(Theme.effectAnimOffset * 0.85, 34);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// animX/animY are Behavior-animated — DankPopout pattern
|
||||||
|
property real animX: 0
|
||||||
|
property real animY: 0
|
||||||
|
property real scaleValue: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
animX = Theme.snap(root._motionActive ? 0 : collapsedMotionX, root.dpr);
|
||||||
|
animY = Theme.snap(root._motionActive ? 0 : collapsedMotionY, root.dpr);
|
||||||
|
scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function on_MotionActiveChanged() {
|
||||||
|
contentContainer.animX = Theme.snap(root._motionActive ? 0 : root._frozenMotionX, root.dpr);
|
||||||
|
contentContainer.animY = Theme.snap(root._motionActive ? 0 : root._frozenMotionY, root.dpr);
|
||||||
|
contentContainer.scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on animX {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: Theme.modalAnimationDuration
|
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on animY {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
|
DankAnim {
|
||||||
|
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||||
|
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scaleValue {
|
||||||
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
|
DankAnim {
|
||||||
|
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||||
|
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shadow mirrors contentWrapper position/scale/opacity
|
||||||
ElevationShadow {
|
ElevationShadow {
|
||||||
id: launcherShadowLayer
|
id: launcherShadowLayer
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
level: Theme.elevationLevel3
|
height: parent.height
|
||||||
fallbackOffset: 6
|
opacity: contentWrapper.opacity
|
||||||
|
scale: contentWrapper.scale
|
||||||
|
x: contentWrapper.x
|
||||||
|
y: contentWrapper.y
|
||||||
|
level: root.shadowLevel
|
||||||
|
fallbackOffset: root.shadowFallbackOffset
|
||||||
targetColor: root.backgroundColor
|
targetColor: root.backgroundColor
|
||||||
borderColor: root.borderColor
|
borderColor: root.borderColor
|
||||||
borderWidth: root.borderWidth
|
borderWidth: root.borderWidth
|
||||||
@@ -403,36 +528,56 @@ Item {
|
|||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ FocusScope {
|
|||||||
|
|
||||||
Controller {
|
Controller {
|
||||||
id: controller
|
id: controller
|
||||||
active: root.parentModal?.spotlightOpen ?? true
|
active: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||||
viewModeContext: root.viewModeContext
|
viewModeContext: root.viewModeContext
|
||||||
|
|
||||||
onItemExecuted: {
|
onItemExecuted: {
|
||||||
@@ -462,7 +462,7 @@ FocusScope {
|
|||||||
showClearButton: true
|
showClearButton: true
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
enabled: root.parentModal ? root.parentModal.spotlightOpen : true
|
enabled: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||||
placeholderText: ""
|
placeholderText: ""
|
||||||
ignoreUpDownKeys: true
|
ignoreUpDownKeys: true
|
||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
@@ -548,7 +548,6 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -697,7 +696,13 @@ FocusScope {
|
|||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
||||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
opacity: {
|
||||||
|
if (!root.parentModal)
|
||||||
|
return 1;
|
||||||
|
if (Theme.isDirectionalEffect && root.parentModal.isClosing)
|
||||||
|
return 1;
|
||||||
|
return root.parentModal.isClosing ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
ResultsList {
|
ResultsList {
|
||||||
id: resultsList
|
id: resultsList
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ DankPopout {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: modalAdapter
|
id: modalAdapter
|
||||||
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
||||||
property bool isClosing: false
|
property bool isClosing: appDrawerPopout.isClosing
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
appDrawerPopout.close();
|
appDrawerPopout.close();
|
||||||
|
|||||||
@@ -126,9 +126,11 @@ DankPopout {
|
|||||||
z: 5000
|
z: 5000
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 200
|
duration: Theme.shortDuration
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1117,6 +1117,7 @@ Item {
|
|||||||
if (!notificationCenterLoader.item) {
|
if (!notificationCenterLoader.item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
notificationCenterLoader.item.triggerScreen = barWindow.screen;
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
const effectiveBarConfig = topBarContent.barConfig;
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||||
if (notificationCenterLoader.item.setBarContext) {
|
if (notificationCenterLoader.item.setBarContext) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ DankPopout {
|
|||||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||||
triggerWidth: 80
|
triggerWidth: 80
|
||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
shouldBeVisible: dashVisible
|
|
||||||
|
|
||||||
property bool __focusArmed: false
|
property bool __focusArmed: false
|
||||||
property bool __contentReady: false
|
property bool __contentReady: false
|
||||||
|
|||||||
@@ -44,6 +44,43 @@ Item {
|
|||||||
|
|
||||||
property int __volumeHoverCount: 0
|
property int __volumeHoverCount: 0
|
||||||
|
|
||||||
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
|
||||||
|
function panelMotionX(panelWidth, active) {
|
||||||
|
if (active)
|
||||||
|
return 0;
|
||||||
|
if (directionalEffect) {
|
||||||
|
const travel = Math.max(Theme.effectAnimOffset, panelWidth * 0.85);
|
||||||
|
return isRightEdge ? -travel : travel;
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
const travel = Math.max(Theme.effectAnimOffset * 0.7, panelWidth * 0.32);
|
||||||
|
return isRightEdge ? -travel : travel;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function panelMotionY(panelType, panelHeight, active) {
|
||||||
|
if (active)
|
||||||
|
return 0;
|
||||||
|
if (directionalEffect) {
|
||||||
|
if (panelType === 2)
|
||||||
|
return panelHeight * 0.08;
|
||||||
|
if (panelType === 3)
|
||||||
|
return -panelHeight * 0.08;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
if (panelType === 2)
|
||||||
|
return panelHeight * 0.04;
|
||||||
|
if (panelType === 3)
|
||||||
|
return -panelHeight * 0.04;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function volumeAreaEntered() {
|
function volumeAreaEntered() {
|
||||||
__volumeHoverCount++;
|
__volumeHoverCount++;
|
||||||
panelEntered();
|
panelEntered();
|
||||||
@@ -62,30 +99,47 @@ Item {
|
|||||||
visible: dropdownType === 1 && volumeAvailable
|
visible: dropdownType === 1 && volumeAvailable
|
||||||
width: 60
|
width: 60
|
||||||
height: 180
|
height: 180
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 1)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(1, height, dropdownType === 1)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
opacity: dropdownType === 1 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : 0)
|
||||||
scale: dropdownType === 1 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,33 +251,50 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: audioDevicesPanel
|
id: audioDevicesPanel
|
||||||
visible: dropdownType === 2
|
visible: dropdownType === 2 && activePlayer !== null
|
||||||
width: 280
|
width: 280
|
||||||
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 2)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(2, height, dropdownType === 2)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
opacity: dropdownType === 2 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : 0)
|
||||||
scale: dropdownType === 2 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,30 +425,47 @@ Item {
|
|||||||
visible: dropdownType === 3
|
visible: dropdownType === 3
|
||||||
width: 240
|
width: 240
|
||||||
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 3)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(3, height, dropdownType === 3)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
opacity: dropdownType === 3 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : 0)
|
||||||
scale: dropdownType === 3 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,9 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400
|
popupWidth: 400
|
||||||
popupHeight: stablePopupHeight
|
popupHeight: stablePopupHeight
|
||||||
positioning: ""
|
positioning: ""
|
||||||
animationScaleCollapsed: 0.94
|
|
||||||
animationOffset: 0
|
|
||||||
suspendShadowWhileResizing: false
|
suspendShadowWhileResizing: false
|
||||||
|
|
||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
|
|||||||
@@ -24,6 +24,29 @@ PanelWindow {
|
|||||||
property real _lastReportedAlignedHeight: -1
|
property real _lastReportedAlignedHeight: -1
|
||||||
property real _storedTopMargin: 0
|
property real _storedTopMargin: 0
|
||||||
property real _storedBottomMargin: 0
|
property real _storedBottomMargin: 0
|
||||||
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real entryTravel: {
|
||||||
|
const base = Math.abs(Theme.effectAnimOffset);
|
||||||
|
if (directionalEffect) {
|
||||||
|
if (isCenterPosition)
|
||||||
|
return Math.max(base, Math.round(content.height * 1.1));
|
||||||
|
return Math.max(base, Math.round(content.width * 0.95));
|
||||||
|
}
|
||||||
|
if (depthEffect)
|
||||||
|
return Math.max(base, 44);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
readonly property real exitTravel: {
|
||||||
|
if (directionalEffect) {
|
||||||
|
if (isCenterPosition)
|
||||||
|
return content.height + entryTravel;
|
||||||
|
return content.width + entryTravel;
|
||||||
|
}
|
||||||
|
if (depthEffect)
|
||||||
|
return Math.round(entryTravel * 1.35);
|
||||||
|
return Anims.slidePx;
|
||||||
|
}
|
||||||
readonly property string clearText: I18n.tr("Dismiss")
|
readonly property string clearText: I18n.tr("Dismiss")
|
||||||
property bool descriptionExpanded: false
|
property bool descriptionExpanded: false
|
||||||
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
||||||
@@ -137,9 +160,9 @@ PanelWindow {
|
|||||||
enabled: !exiting && !_isDestroying
|
enabled: !exiting && !_isDestroying
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
id: implicitHeightAnim
|
id: implicitHeightAnim
|
||||||
duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
|
duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasized
|
easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,9 +934,9 @@ PanelWindow {
|
|||||||
if (isCenterPosition)
|
if (isCenterPosition)
|
||||||
return 0;
|
return 0;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
return isLeft ? -entryTravel : entryTravel;
|
||||||
}
|
}
|
||||||
y: isTopCenter ? -Anims.slidePx : isBottomCenter ? Anims.slidePx : 0
|
y: isTopCenter ? -entryTravel : isBottomCenter ? entryTravel : 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -925,16 +948,16 @@ PanelWindow {
|
|||||||
property: isCenterPosition ? "y" : "x"
|
property: isCenterPosition ? "y" : "x"
|
||||||
from: {
|
from: {
|
||||||
if (isTopCenter)
|
if (isTopCenter)
|
||||||
return -Anims.slidePx;
|
return -entryTravel;
|
||||||
if (isBottomCenter)
|
if (isBottomCenter)
|
||||||
return Anims.slidePx;
|
return entryTravel;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
return isLeft ? -entryTravel : entryTravel;
|
||||||
}
|
}
|
||||||
to: 0
|
to: 0
|
||||||
duration: Theme.notificationEnterDuration
|
duration: Theme.variantDuration(Theme.notificationEnterDuration, true)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: isCenterPosition ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantPopoutEnterCurve
|
||||||
onStopped: {
|
onStopped: {
|
||||||
if (!win.exiting && !win._isDestroying) {
|
if (!win.exiting && !win._isDestroying) {
|
||||||
if (isCenterPosition) {
|
if (isCenterPosition) {
|
||||||
@@ -959,35 +982,35 @@ PanelWindow {
|
|||||||
from: 0
|
from: 0
|
||||||
to: {
|
to: {
|
||||||
if (isTopCenter)
|
if (isTopCenter)
|
||||||
return -Anims.slidePx;
|
return -exitTravel;
|
||||||
if (isBottomCenter)
|
if (isBottomCenter)
|
||||||
return Anims.slidePx;
|
return exitTravel;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
return isLeft ? -exitTravel : exitTravel;
|
||||||
}
|
}
|
||||||
duration: Theme.notificationExitDuration
|
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
|
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: content
|
target: content
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0
|
to: Theme.isDirectionalEffect ? 1 : 0
|
||||||
duration: Theme.notificationExitDuration
|
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.standardAccel
|
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: content
|
target: content
|
||||||
property: "scale"
|
property: "scale"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0.98
|
to: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed
|
||||||
duration: Theme.notificationExitDuration
|
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
|
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,144 @@ Item {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "typography"
|
||||||
|
tags: ["animation", "variant", "style", "slide", "fluent", "dynamic", "motion"]
|
||||||
|
title: I18n.tr("Animation Style")
|
||||||
|
settingKey: "animationVariant"
|
||||||
|
iconName: "auto_awesome_motion"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: animVariantGroup.implicitHeight
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: animVariantGroup
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
|
||||||
|
minButtonWidth: parent.width < 480 ? 64 : 96
|
||||||
|
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||||
|
model: [I18n.tr("Material"), I18n.tr("Fluent"), I18n.tr("Dynamic")]
|
||||||
|
selectionMode: "single"
|
||||||
|
currentIndex: SettingsData.animationVariant
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
SettingsData.set("animationVariant", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onAnimationVariantChanged() {
|
||||||
|
animVariantGroup.currentIndex = SettingsData.animationVariant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.15
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: variantDescription.implicitHeight + Theme.spacingS * 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: variantDescription
|
||||||
|
x: Theme.spacingM
|
||||||
|
y: Theme.spacingS
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: {
|
||||||
|
switch (SettingsData.animationVariant) {
|
||||||
|
case 1:
|
||||||
|
return I18n.tr("Fluent: Smooth cubic deceleration in, quick snap out — clean, elegant curves.");
|
||||||
|
case 2:
|
||||||
|
return I18n.tr("Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Material: Material Design 3 Expressive bezier curves. The DMS default feel.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "typography"
|
||||||
|
tags: ["animation", "motion", "effect", "slide", "directional", "depth", "spring", "physics"]
|
||||||
|
title: I18n.tr("Motion Effects")
|
||||||
|
settingKey: "motionEffect"
|
||||||
|
iconName: "motion_photos_on"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: motionEffectGroup.implicitHeight
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: motionEffectGroup
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
|
||||||
|
minButtonWidth: parent.width < 480 ? 64 : 96
|
||||||
|
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||||
|
model: [I18n.tr("Standard"), I18n.tr("Directional"), I18n.tr("Depth")]
|
||||||
|
selectionMode: "single"
|
||||||
|
currentIndex: SettingsData.motionEffect
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected)
|
||||||
|
return;
|
||||||
|
SettingsData.set("motionEffect", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onMotionEffectChanged() {
|
||||||
|
motionEffectGroup.currentIndex = SettingsData.motionEffect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.15
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: motionEffectDescription.implicitHeight + Theme.spacingS * 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: motionEffectDescription
|
||||||
|
x: Theme.spacingM
|
||||||
|
y: Theme.spacingS
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: {
|
||||||
|
switch (SettingsData.motionEffect) {
|
||||||
|
case 1:
|
||||||
|
return I18n.tr("Directional: Panels glide in from a larger distance at full size — no scale change, pure clean motion.");
|
||||||
|
case 2:
|
||||||
|
return I18n.tr("Depth: Panels scale up from small as they slide in — a dramatic pop-forward depth effect.");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Standard: Classic Material Design 3 — panels rise from below with a subtle scale. The DMS default.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "typography"
|
tab: "typography"
|
||||||
tags: ["font", "family", "text", "typography"]
|
tags: ["font", "family", "text", "typography"]
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ Scope {
|
|||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,45 +154,69 @@ Scope {
|
|||||||
id: scaleTransform
|
id: scaleTransform
|
||||||
origin.x: contentContainer.width / 2
|
origin.x: contentContainer.width / 2
|
||||||
origin.y: contentContainer.height / 2
|
origin.y: contentContainer.height / 2
|
||||||
xScale: overviewScope.overviewOpen ? 1 : 0.96
|
xScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||||
yScale: overviewScope.overviewOpen ? 1 : 0.96
|
yScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||||
|
|
||||||
Behavior on xScale {
|
Behavior on xScale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on yScale {
|
Behavior on yScale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Translate {
|
Translate {
|
||||||
id: motionTransform
|
id: motionTransform
|
||||||
x: 0
|
x: {
|
||||||
y: overviewScope.overviewOpen ? 0 : Theme.spacingL
|
if (overviewScope.overviewOpen)
|
||||||
|
return 0;
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return 0;
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return Theme.effectAnimOffset * 0.25;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
y: {
|
||||||
|
if (overviewScope.overviewOpen)
|
||||||
|
return 0;
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return -Math.max(contentContainer.height * 0.8, Theme.effectAnimOffset * 1.1);
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return Math.max(Theme.effectAnimOffset * 0.85, 28);
|
||||||
|
return Theme.effectAnimOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,8 +202,18 @@ Scope {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: spotlightContainer
|
id: spotlightContainer
|
||||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real collapsedMotionX: depthEffect ? Theme.effectAnimOffset * 0.25 : 0
|
||||||
|
readonly property real collapsedMotionY: {
|
||||||
|
if (directionalEffect)
|
||||||
|
return Math.max(height * 0.85, Theme.effectAnimOffset * 1.1);
|
||||||
|
if (depthEffect)
|
||||||
|
return Math.max(Theme.effectAnimOffset * 0.8, 30);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
x: Theme.snap((parent.width - width) / 2 + (overlayWindow.shouldShowSpotlight ? 0 : collapsedMotionX), overlayWindow.dpr)
|
||||||
|
y: Theme.snap((parent.height - height) / 2 + (overlayWindow.shouldShowSpotlight ? 0 : collapsedMotionY), overlayWindow.dpr)
|
||||||
|
|
||||||
readonly property int baseWidth: {
|
readonly property int baseWidth: {
|
||||||
switch (SettingsData.dankLauncherV2Size) {
|
switch (SettingsData.dankLauncherV2Size) {
|
||||||
@@ -234,8 +244,8 @@ Scope {
|
|||||||
|
|
||||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||||
|
|
||||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1.0 : Theme.effectScaleCollapsed)
|
||||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1 : 0)
|
||||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||||
enabled: overlayWindow.shouldShowSpotlight
|
enabled: overlayWindow.shouldShowSpotlight
|
||||||
|
|
||||||
@@ -245,10 +255,11 @@ Scope {
|
|||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
id: scaleAnimation
|
id: scaleAnimation
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.fast
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (running || !spotlightContainer.animatingOut)
|
if (running || !spotlightContainer.animatingOut)
|
||||||
return;
|
return;
|
||||||
@@ -258,10 +269,27 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.fast
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,30 +62,30 @@ Item {
|
|||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,16 +124,16 @@ Item {
|
|||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ Item {
|
|||||||
property string triggerSection: ""
|
property string triggerSection: ""
|
||||||
property string positioning: "center"
|
property string positioning: "center"
|
||||||
property int animationDuration: Theme.popoutAnimationDuration
|
property int animationDuration: Theme.popoutAnimationDuration
|
||||||
property real animationScaleCollapsed: 0.96
|
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||||
property real animationOffset: Theme.spacingL
|
property real animationOffset: Theme.effectAnimOffset
|
||||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
|
||||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||||
property bool suspendShadowWhileResizing: false
|
property bool suspendShadowWhileResizing: false
|
||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
@@ -74,6 +74,7 @@ Item {
|
|||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
property var _lastOpenedScreen: null
|
property var _lastOpenedScreen: null
|
||||||
|
property bool isClosing: false
|
||||||
|
|
||||||
property int effectiveBarPosition: 0
|
property int effectiveBarPosition: 0
|
||||||
property real effectiveBarBottomGap: 0
|
property real effectiveBarBottomGap: 0
|
||||||
@@ -156,10 +157,14 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool animationsEnabled: true
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
|
isClosing = false;
|
||||||
|
animationsEnabled = false;
|
||||||
|
|
||||||
// Snapshot mask geometry
|
// Snapshot mask geometry
|
||||||
_frozenMaskX = maskX;
|
_frozenMaskX = maskX;
|
||||||
@@ -174,12 +179,22 @@ Item {
|
|||||||
}
|
}
|
||||||
_lastOpenedScreen = screen;
|
_lastOpenedScreen = screen;
|
||||||
|
|
||||||
shouldBeVisible = true;
|
if (contentContainer) {
|
||||||
|
contentContainer.animX = Theme.snap(contentContainer.offsetX, root.dpr);
|
||||||
|
contentContainer.animY = Theme.snap(contentContainer.offsetY, root.dpr);
|
||||||
|
contentContainer.scaleValue = root.animationScaleCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
if (useBackgroundWindow) {
|
if (useBackgroundWindow) {
|
||||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||||
|
backgroundWindow.visible = true;
|
||||||
}
|
}
|
||||||
|
contentWindow.visible = true;
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
|
animationsEnabled = true;
|
||||||
|
shouldBeVisible = true;
|
||||||
if (shouldBeVisible && screen) {
|
if (shouldBeVisible && screen) {
|
||||||
if (useBackgroundWindow)
|
if (useBackgroundWindow)
|
||||||
backgroundWindow.visible = true;
|
backgroundWindow.visible = true;
|
||||||
@@ -191,6 +206,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
isClosing = true;
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
_primeContent = false;
|
_primeContent = false;
|
||||||
PopoutManager.popoutChanged();
|
PopoutManager.popoutChanged();
|
||||||
@@ -222,9 +238,10 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeTimer
|
id: closeTimer
|
||||||
interval: animationDuration
|
interval: Theme.variantCloseInterval(animationDuration)
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
|
isClosing = false;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (useBackgroundWindow)
|
if (useBackgroundWindow)
|
||||||
backgroundWindow.visible = false;
|
backgroundWindow.visible = false;
|
||||||
@@ -241,7 +258,13 @@ Item {
|
|||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
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: Math.max(0, animationOffset)
|
readonly property real shadowMotionPadding: {
|
||||||
|
if (Theme.isDirectionalEffect)
|
||||||
|
return Math.max(0, animationOffset) + 16;
|
||||||
|
if (Theme.isDepthEffect)
|
||||||
|
return Math.max(0, animationOffset) + 8;
|
||||||
|
return Math.max(0, animationOffset);
|
||||||
|
}
|
||||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||||
@@ -353,6 +376,10 @@ Item {
|
|||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: maskRect
|
item: maskRect
|
||||||
|
Region {
|
||||||
|
item: contentExclusionRect
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -361,26 +388,70 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root._frozenMaskX
|
x: root._frozenMaskX
|
||||||
y: root._frozenMaskY
|
y: root._frozenMaskY
|
||||||
width: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
width: (backgroundWindow.visible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||||
height: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
height: (backgroundWindow.visible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Item {
|
||||||
|
id: contentExclusionRect
|
||||||
|
visible: false
|
||||||
|
x: root.alignedX
|
||||||
|
y: root.alignedY
|
||||||
|
width: root.alignedWidth
|
||||||
|
height: root.alignedHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: outsideClickCatcher
|
||||||
x: root._frozenMaskX
|
x: root._frozenMaskX
|
||||||
y: root._frozenMaskY
|
y: root._frozenMaskY
|
||||||
width: root._frozenMaskWidth
|
width: root._frozenMaskWidth
|
||||||
height: root._frozenMaskHeight
|
height: root._frozenMaskHeight
|
||||||
hoverEnabled: false
|
enabled: root.shouldBeVisible && root.backgroundInteractive
|
||||||
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)
|
readonly property real contentLeft: Math.max(0, root.alignedX - x)
|
||||||
return;
|
readonly property real contentTop: Math.max(0, root.alignedY - y)
|
||||||
backgroundClicked();
|
readonly property real contentRight: Math.min(width, contentLeft + root.alignedWidth)
|
||||||
|
readonly property real contentBottom: Math.min(height, contentTop + root.alignedHeight)
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: outsideClickCatcher.width
|
||||||
|
height: Math.max(0, outsideClickCatcher.contentTop)
|
||||||
|
enabled: parent.enabled
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
x: 0
|
||||||
|
y: outsideClickCatcher.contentBottom
|
||||||
|
width: outsideClickCatcher.width
|
||||||
|
height: Math.max(0, outsideClickCatcher.height - outsideClickCatcher.contentBottom)
|
||||||
|
enabled: parent.enabled
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
x: 0
|
||||||
|
y: outsideClickCatcher.contentTop
|
||||||
|
width: Math.max(0, outsideClickCatcher.contentLeft)
|
||||||
|
height: Math.max(0, outsideClickCatcher.contentBottom - outsideClickCatcher.contentTop)
|
||||||
|
enabled: parent.enabled
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
x: outsideClickCatcher.contentRight
|
||||||
|
y: outsideClickCatcher.contentTop
|
||||||
|
width: Math.max(0, outsideClickCatcher.width - outsideClickCatcher.contentRight)
|
||||||
|
height: Math.max(0, outsideClickCatcher.contentBottom - outsideClickCatcher.contentTop)
|
||||||
|
enabled: parent.enabled
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onClicked: root.backgroundClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,7 +496,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
|
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
@@ -483,13 +553,65 @@ Item {
|
|||||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||||
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
||||||
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
||||||
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real directionalTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||||
|
readonly property real directionalTravelY: Math.max(root.animationOffset, root.alignedHeight + 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 offsetX: {
|
||||||
|
if (directionalEffect) {
|
||||||
|
if (barLeft)
|
||||||
|
return -directionalTravelX;
|
||||||
|
if (barRight)
|
||||||
|
return directionalTravelX;
|
||||||
|
if (barTop || barBottom)
|
||||||
|
return 0;
|
||||||
|
return sectionTilt * directionalTravelX * 0.2;
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
if (barLeft)
|
||||||
|
return -depthTravel;
|
||||||
|
if (barRight)
|
||||||
|
return depthTravel;
|
||||||
|
if (barTop || barBottom)
|
||||||
|
return 0;
|
||||||
|
return sectionTilt * depthTravel * 0.2;
|
||||||
|
}
|
||||||
|
return barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0);
|
||||||
|
}
|
||||||
|
readonly property real offsetY: {
|
||||||
|
if (directionalEffect) {
|
||||||
|
if (barBottom)
|
||||||
|
return directionalTravelY;
|
||||||
|
if (barTop)
|
||||||
|
return -directionalTravelY;
|
||||||
|
if (barLeft || barRight)
|
||||||
|
return 0;
|
||||||
|
return directionalTravelY;
|
||||||
|
}
|
||||||
|
if (depthEffect) {
|
||||||
|
if (barBottom)
|
||||||
|
return depthTravel;
|
||||||
|
if (barTop)
|
||||||
|
return -depthTravel;
|
||||||
|
if (barLeft || barRight)
|
||||||
|
return 0;
|
||||||
|
return depthTravel;
|
||||||
|
}
|
||||||
|
return barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0);
|
||||||
|
}
|
||||||
|
|
||||||
property real animX: 0
|
property real animX: 0
|
||||||
property real animY: 0
|
property real animY: 0
|
||||||
property real scaleValue: root.animationScaleCollapsed
|
property real scaleValue: root.animationScaleCollapsed
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr);
|
||||||
|
animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr);
|
||||||
|
scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||||
|
|
||||||
@@ -503,24 +625,27 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Behavior on animX {
|
Behavior on animX {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
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
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
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
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
@@ -544,10 +669,9 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentWrapper
|
id: contentWrapper
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
opacity: shouldBeVisible ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
scale: contentContainer.scaleValue
|
scale: contentContainer.scaleValue
|
||||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||||
@@ -558,8 +682,9 @@ Item {
|
|||||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: animationDuration
|
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user