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

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

- Shadow system rewrite with SDF quads
- Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders
- Improve frame blur performance
- Plugin performance gate
This commit is contained in:
purian23
2026-06-12 11:03:39 -04:00
parent 08fd6e26d8
commit d53809cf2b
52 changed files with 2588 additions and 2804 deletions
@@ -1,5 +1,4 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:bluetooth-pairing"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property string deviceName: ""
property string deviceAddress: ""
property string requestType: ""
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Clipboard
import qs.Modals.Common
@@ -12,11 +11,6 @@ DankModal {
layerNamespace: "dms:clipboard"
HyprlandFocusGrab {
windows: [clipboardHistoryModal.contentWindow]
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
}
function toggle() {
if (shouldBeVisible) {
hide();
@@ -64,6 +58,7 @@ DankModal {
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
visible: false
keepContentLoaded: true
modalWidth: ClipboardConstants.modalWidth
modalHeight: ClipboardConstants.modalHeight
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
+7 -3
View File
@@ -1,4 +1,5 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Services
@@ -52,8 +53,13 @@ Item {
focus: true
anchors.fill: parent
}
// Hyprland OnDemand grab delivers keyboard focus to the modal content surface.
HyprlandFocusGrab {
windows: root.contentWindow ? [root.contentWindow] : []
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
}
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
@@ -96,8 +102,6 @@ Item {
}
}
// Defer Loader source-component swap until impl is fully closed; avoids
// tearing down a modal mid-animation when frame mode is toggled.
function _maybeResolveBackend() {
if (_resolvedBackend === _desiredBackend)
return;
+259 -334
View File
@@ -31,7 +31,6 @@ Item {
property bool closeOnBackgroundClick: true
property string animationType: "scale"
// Opposite side from the launcher by default; subclasses may override
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
@@ -87,16 +86,13 @@ Item {
property real frozenMotionOffsetX: 0
property real frozenMotionOffsetY: 0
readonly property alias contentWindow: contentWindow
readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: false
readonly property bool useSingleWindow: CompositorService.isHyprland
signal opened
signal dialogClosed
signal backgroundClicked
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer {
id: _syncTimer
interval: 0
@@ -115,6 +111,7 @@ Item {
id: modalChrome
modalHandle: root.modalHandle
claimPrefix: root.layerNamespace + ":modal"
surfaceKind: "modal"
screenName: root._currentScreenName()
enabled: root.frameOwnsConnectedChrome
active: root.shouldBeVisible
@@ -125,17 +122,38 @@ Item {
}
function _publishModalChromeState() {
const presented = shouldBeVisible || contentWindow.visible;
const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open"));
const bodyRect = {
"x": alignedX,
"y": alignedY,
"width": alignedWidth,
"height": alignedHeight
};
const animationOffset = {
"x": modalContainer ? modalContainer.animX : 0,
"y": modalContainer ? modalContainer.animY : 0
};
const state = {
"visible": shouldBeVisible || contentWindow.visible,
"kind": "modal",
"screenName": root._currentScreenName(),
"phase": phase,
"visible": presented,
"presented": presented,
"barSide": resolvedConnectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": alignedX,
"bodyY": alignedY,
"bodyW": alignedWidth,
"bodyH": alignedHeight,
"animX": modalContainer ? modalContainer.animX : 0,
"animY": modalContainer ? modalContainer.animY : 0,
"animX": animationOffset.x,
"animY": animationOffset.y,
"omitStartConnector": false,
"omitEndConnector": false
"omitEndConnector": false,
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
};
return modalChrome.publish(state);
}
@@ -222,22 +240,16 @@ Item {
const focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) {
contentWindow.screen = focusedScreen;
if (!useSingleWindow)
clickCatcher.screen = focusedScreen;
}
ModalManager.openModal(modalHandle);
if (Theme.isDirectionalEffect || root.useBackground) {
if (!useSingleWindow)
clickCatcher.visible = true;
contentWindow.visible = true;
}
Qt.callLater(() => {
animationsEnabled = true;
shouldBeVisible = true;
if (!useSingleWindow && !clickCatcher.visible)
clickCatcher.visible = true;
if (!contentWindow.visible)
contentWindow.visible = true;
opened();
@@ -264,8 +276,6 @@ Item {
ModalManager.closeModal(modalHandle);
closeTimer.stop();
contentWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed();
Qt.callLater(() => animationsEnabled = true);
}
@@ -304,8 +314,6 @@ Item {
const newScreen = CompositorService.getFocusedScreen();
if (newScreen) {
contentWindow.screen = newScreen;
if (!useSingleWindow)
clickCatcher.screen = newScreen;
}
}
}
@@ -317,29 +325,12 @@ Item {
if (shouldBeVisible)
return;
contentWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed();
}
}
// shadowRenderPadding is zeroed when frame owns the chrome
// Wayland then clips any content translating past
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: {
if (frameOwnsConnectedChrome)
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 alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
@@ -349,7 +340,6 @@ Item {
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) {
case "top":
@@ -412,57 +402,6 @@ Item {
}
})(), dpr)
PanelWindow {
id: clickCatcher
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
item: Rectangle {
x: root.alignedX
y: root.alignedY
width: root.alignedWidth
height: root.alignedHeight
}
intersection: Intersection.Xor
}
MouseArea {
anchors.fill: parent
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: root.backgroundClicked()
}
Rectangle {
anchors.fill: parent
z: -1
color: "black"
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: opacity > 0
Behavior on opacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
}
PanelWindow {
id: contentWindow
visible: false
@@ -472,8 +411,8 @@ Item {
targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, modalContainer.scaleValue)
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurX: connectedReveal.x + modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
blurY: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
blurRadius: root.effectiveCornerRadius
@@ -487,36 +426,15 @@ Item {
"error": true
})
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldHaveFocus)
return WlrKeyboardFocus.None;
if (root.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
anchors {
left: true
top: true
right: root.useSingleWindow
bottom: root.useSingleWindow
right: true
bottom: true
}
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
WlrLayershell.margins {
left: actualMarginLeft
top: actualMarginTop
right: 0
bottom: 0
}
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: {
if (visible)
return;
@@ -528,7 +446,7 @@ Item {
MouseArea {
anchors.fill: parent
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
z: -2
onClicked: root.backgroundClicked()
}
@@ -537,7 +455,7 @@ Item {
anchors.fill: parent
z: -1
color: "black"
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: opacity > 0
Behavior on opacity {
@@ -551,249 +469,256 @@ Item {
}
Item {
id: modalContainer
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
id: connectedReveal
// Clip to final footprint while frame-owned chrome grows from the bar edge.
x: root.alignedX
y: root.alignedY
width: root.alignedWidth
height: root.alignedHeight
MouseArea {
anchors.fill: parent
enabled: root.useSingleWindow && root.shouldBeVisible
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
readonly property bool slide: root.animationType === "slide"
readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
readonly property real customDistLeft: customAnchorX
readonly property real customDistRight: root.screenWidth - customAnchorX
readonly property real customDistTop: customAnchorY
readonly property real customDistBottom: root.screenHeight - customAnchorY
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
readonly property real offsetX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left":
return -connectedEmergenceTravelX;
case "right":
return connectedEmergenceTravelX;
}
return 0;
}
if (slide && !directionalEffect && !depthEffect)
return 15;
if (directionalEffect) {
switch (root.positioning) {
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 (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top":
return -connectedEmergenceTravelY;
case "bottom":
return connectedEmergenceTravelY;
}
return 0;
}
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;
}
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
// openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject {
id: morph
property real openProgress: root.shouldBeVisible ? 1 : 0
Behavior on openProgress {
enabled: root.animationsEnabled
NumberAnimation {
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
onAnimXChanged: if (root.frameOwnsConnectedChrome)
root._queueAnimSync()
onAnimYChanged: if (root.frameOwnsConnectedChrome)
root._queueAnimSync()
clip: root.frameOwnsConnectedChrome
Item {
id: contentContainer
anchors.centerIn: parent
width: parent.width
height: parent.height
clip: false
id: modalContainer
x: Theme.snap(animX, root.dpr)
y: Theme.snap(animY, root.dpr)
width: root.alignedWidth
height: root.alignedHeight
MouseArea {
anchors.fill: parent
enabled: root.shouldBeVisible
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
readonly property bool slide: root.animationType === "slide"
readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
readonly property real customDistLeft: customAnchorX
readonly property real customDistRight: root.screenWidth - customAnchorX
readonly property real customDistTop: customAnchorY
readonly property real customDistBottom: root.screenHeight - customAnchorY
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
readonly property real offsetX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left":
return -connectedEmergenceTravelX;
case "right":
return connectedEmergenceTravelX;
}
return 0;
}
if (slide && !directionalEffect && !depthEffect)
return 15;
if (directionalEffect) {
switch (root.positioning) {
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 (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top":
return -connectedEmergenceTravelY;
case "bottom":
return connectedEmergenceTravelY;
}
return 0;
}
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:
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;
}
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
QtObject {
id: morph
property real openProgress: root.shouldBeVisible ? 1 : 0
Behavior on openProgress {
enabled: root.animationsEnabled
NumberAnimation {
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
onAnimXChanged: if (root.frameOwnsConnectedChrome)
root._queueAnimSync()
onAnimYChanged: if (root.frameOwnsConnectedChrome)
root._queueAnimSync()
Item {
id: animatedContent
anchors.fill: parent
id: contentContainer
anchors.centerIn: parent
width: parent.width
height: parent.height
clip: false
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
scale: modalContainer.scaleValue
transformOrigin: Item.Center
Behavior on opacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on publishedOpacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
ElevationShadow {
id: modalShadowLayer
Item {
id: animatedContent
anchors.fill: parent
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
Rectangle {
anchors.fill: parent
radius: root.effectiveCornerRadius
color: "transparent"
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
z: 100
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
clip: false
Item {
id: directContentWrapper
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
scale: modalContainer.scaleValue
transformOrigin: Item.Center
Behavior on opacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on publishedOpacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
ElevationShadow {
id: modalShadowLayer
anchors.fill: parent
visible: root.directContent !== null
focus: true
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
Rectangle {
anchors.fill: parent
radius: root.effectiveCornerRadius
color: "transparent"
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
z: 100
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
Item {
id: directContentWrapper
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Connections {
target: root
function onDirectContentChanged() {
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
Connections {
target: root
function onDirectContentChanged() {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
}
}
}
Loader {
id: contentLoader
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
Loader {
id: contentLoader
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus());
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus());
}
}
}
}
@@ -205,6 +205,7 @@ Item {
id: clickCatcher
visible: false
color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
WlrLayershell.layer: WlrLayershell.Top
@@ -259,15 +260,7 @@ Item {
"error": true
})
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldHaveFocus)
return WlrKeyboardFocus.None;
if (root.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
anchors {
left: true
@@ -1,6 +1,5 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import qs.Common
import qs.Modals.Common
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:color-picker"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property string pickerTitle: I18n.tr("Choose Color")
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null
@@ -30,7 +30,6 @@ Item {
property string _pendingMode: ""
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
// Animation state — matches DankPopout/DankModal pattern
property bool animationsEnabled: true
property bool _motionActive: false
property real _frozenMotionX: 0
@@ -108,8 +107,6 @@ Item {
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize.
// Positions the modal flush to the emerge side, centered on the cross axis.
readonly property var _connectedModalPos: {
const fallback = {
"x": (screenWidth - modalWidth) / 2,
@@ -175,8 +172,6 @@ Item {
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
// Shadow padding for the content window (render padding only, no motion padding).
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
@@ -203,29 +198,11 @@ Item {
}
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
// 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.isConnectedEffect) || Theme.isDepthEffect
// Content window geometry
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr))
readonly property real _cwWidth: alignedWidth + shadowPad * 2
readonly property real _cwHeight: {
if (launcherArcExtenderActive)
return _connectedChromeHeight;
if (Theme.isDirectionalEffect && !Theme.isConnectedEffect)
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: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad)
readonly property real _ccX: _connectedChromeX
readonly property real _ccY: _connectedChromeY
signal dialogClosed
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer {
id: _syncTimer
interval: 0
@@ -242,6 +219,7 @@ Item {
id: modalChrome
modalHandle: root.modalHandle
claimPrefix: "dms:launcher-v2"
surfaceKind: "launcher"
screenName: root._currentScreenName()
enabled: root.frameOwnsConnectedChrome
active: root.spotlightOpen
@@ -252,17 +230,38 @@ Item {
}
function _publishModalChromeState() {
const presented = spotlightOpen || contentWindow.visible;
const phase = !presented ? "hidden" : (isClosing ? "closing" : (!contentWindow.visible ? "opening" : "open"));
const bodyRect = {
"x": _connectedChromeX,
"y": _connectedChromeY,
"width": _connectedChromeWidth,
"height": _connectedChromeHeight
};
const animationOffset = {
"x": contentContainer ? contentContainer.animX : 0,
"y": contentContainer ? contentContainer.animY : 0
};
const state = {
"visible": spotlightOpen || contentWindow.visible,
"kind": "launcher",
"screenName": root._currentScreenName(),
"phase": phase,
"visible": presented,
"presented": presented,
"barSide": resolvedConnectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": _connectedChromeX,
"bodyY": _connectedChromeY,
"bodyW": _connectedChromeWidth,
"bodyH": _connectedChromeHeight,
"animX": contentContainer ? contentContainer.animX : 0,
"animY": contentContainer ? contentContainer.animY : 0,
"animX": animationOffset.x,
"animY": animationOffset.y,
"omitStartConnector": false,
"omitEndConnector": false
"omitEndConnector": false,
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
};
return modalChrome.publish(state);
}
@@ -359,8 +358,6 @@ Item {
return;
contentVisible = true;
spotlightContent.closeTransientUi?.();
// NOTE: forceActiveFocus() is deliberately NOT called here.
// It is deferred to after animation starts to avoid compositor IPC stalls.
if (spotlightContent.searchField) {
spotlightContent.searchField.text = query;
@@ -398,40 +395,29 @@ Item {
isClosing = false;
openedFromOverview = false;
// Disable animations so the snap is instant
animationsEnabled = false;
// Freeze the collapsed offsets (they depend on height which could change)
_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(modalHandle);
spotlightOpen = true;
backgroundWindow.visible = true;
contentWindow.visible = true;
if (useHyprlandFocusGrab)
focusGrab.active = true;
// Load content and initialize (but no forceActiveFocus — that's deferred)
_ensureContentLoadedAndInitialize(query || "", mode || "");
// Frame 1: enable animations and trigger enter motion
// Defer focus until after enter motion starts (avoids compositor IPC stalls).
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)
@@ -454,16 +440,13 @@ Item {
spotlightContent?.closeTransientUi?.();
openedFromOverview = false;
isClosing = true;
// 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;
spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle);
closeCleanupTimer.start();
}
@@ -500,7 +483,6 @@ Item {
isClosing = false;
contentVisible = false;
contentWindow.visible = false;
backgroundWindow.visible = false;
if (root.unloadContentOnClose)
launcherContentLoader.active = false;
dialogClosed();
@@ -519,7 +501,7 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [contentWindow]
active: false
active: root.useHyprlandFocusGrab && root.spotlightOpen
onCleared: {
if (spotlightOpen) {
@@ -569,7 +551,6 @@ Item {
root._releaseModalChrome();
root._windowEnabled = false;
backgroundWindow.screen = newScreen;
contentWindow.screen = newScreen;
Qt.callLater(() => {
root._windowEnabled = true;
@@ -577,73 +558,6 @@ Item {
}
}
PanelWindow {
id: backgroundWindow
visible: false
color: "transparent"
readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
WlrLayershell.namespace: "dms:spotlight:bg"
WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.margins {
top: backgroundWindow._topMargin
bottom: backgroundWindow._bottomMargin
left: backgroundWindow._leftMargin
right: backgroundWindow._rightMargin
}
anchors {
top: true
bottom: true
left: true
right: true
}
mask: Region {
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
Region {
item: bgContentHole
intersection: Intersection.Subtract
}
}
Item {
id: bgFullScreenMask
anchors.fill: parent
}
Item {
id: bgContentHole
visible: false
x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin
y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin
width: root.alignedWidth
height: root.contentSurfaceHeight
}
Rectangle {
id: backgroundDarken
anchors.fill: parent
color: "black"
opacity: 0
visible: false
}
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
onClicked: root.hide()
}
}
PanelWindow {
id: contentWindow
visible: false
@@ -663,23 +577,31 @@ Item {
WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors {
left: true
top: true
right: true
bottom: true
}
WlrLayershell.margins {
left: root._cwMarginLeft
top: root._cwMarginTop
}
implicitWidth: root._cwWidth
implicitHeight: root._cwHeight
mask: Region {
item: contentInputMask
item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask
Region {
item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null
}
}
Item {
id: dismissArea
visible: false
anchors.fill: parent
anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
}
Item {
@@ -691,16 +613,31 @@ Item {
height: root.contentSurfaceHeight
}
MouseArea {
anchors.fill: dismissArea
enabled: root.spotlightOpen
z: -2
onClicked: root.hide()
}
Item {
id: contentContainer
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
x: root._ccX
y: root._ccY
width: root.alignedWidth
height: root.contentSurfaceHeight
MouseArea {
anchors.fill: parent
enabled: root.spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
readonly property bool dockTop: dockEdge === 0
readonly property bool dockBottom: dockEdge === 1
@@ -755,7 +692,6 @@ Item {
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
}
// openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject {
id: morph
property real openProgress: root._motionActive ? 1 : 0
@@ -814,7 +750,6 @@ Item {
width: contentContainer.width
height: contentContainer.height
// Shadow mirrors contentWrapper position/scale/opacity
ElevationShadow {
id: launcherShadowLayer
width: parent.width
@@ -832,7 +767,6 @@ Item {
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
// contentWrapper moves inside static contentContainer — DankPopout pattern
Item {
id: contentWrapper
width: parent.width
@@ -84,14 +84,14 @@ Item {
readonly property real alignedX: Theme.snap(modalX, dpr)
readonly property real alignedY: Theme.snap(modalY, dpr)
// Extra headroom above the window for the slide-in animation
// Extra headroom above the content for the slide-in animation
readonly property real _animHeadroom: 16
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
readonly property real windowWidth: alignedWidth + contentX + shadowPad
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
readonly property real windowWidth: alignedWidth + contentX + shadowPad
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
@@ -114,6 +114,7 @@ Item {
}
}
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
signal dialogClosed
@@ -164,8 +165,6 @@ Item {
openedFromOverview = false;
keyboardActive = true;
ModalManager.openModal(modalHandle);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize(query || "", mode || "");
}
@@ -201,7 +200,6 @@ Item {
contentVisible = false;
keyboardActive = false;
spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle);
closeCleanupTimer.start();
}
@@ -231,7 +229,7 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [launcherWindow]
active: false
active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: {
if (spotlightOpen)
hide();
@@ -270,8 +268,9 @@ Item {
PanelWindow {
id: clickCatcher
screen: launcherWindow.screen
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
WlrLayershell.layer: root.effectiveLauncherLayer
@@ -337,24 +336,24 @@ Item {
WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors {
top: true
left: true
right: root.useBackgroundDarken
bottom: root.useBackgroundDarken
right: root.useSingleWindow
bottom: root.useSingleWindow
}
WlrLayershell.margins {
left: root.useBackgroundDarken ? 0 : root.windowX
top: root.useBackgroundDarken ? 0 : root.windowY
left: root.useSingleWindow ? 0 : root.windowX
top: root.useSingleWindow ? 0 : root.windowY
right: 0
bottom: 0
}
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
mask: Region {
item: inputMask
@@ -364,15 +363,15 @@ Item {
id: inputMask
visible: false
color: "transparent"
x: root.useBackgroundDarken ? 0 : modalContainer.x
y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset
width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth
height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH
x: root.useSingleWindow ? 0 : modalContainer.x
y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset
width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth
height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH
}
MouseArea {
anchors.fill: parent
enabled: root.useBackgroundDarken && spotlightOpen
enabled: root.useSingleWindow && spotlightOpen
z: -2
onClicked: root.hide()
}
@@ -396,13 +395,23 @@ Item {
Item {
id: modalContainer
x: root.useBackgroundDarken ? root.alignedX : root.contentX
y: root.useBackgroundDarken ? root.alignedY : root.contentY
x: root.useSingleWindow ? root.alignedX : root.contentX
y: root.useSingleWindow ? root.alignedY : root.contentY
width: root.alignedWidth
height: root._animatedContentH
visible: _renderActive
z: 0
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
property bool _renderActive: contentVisible
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
@@ -80,6 +80,7 @@ Item {
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
"allow": ["top", "overlay"],
@@ -172,8 +173,6 @@ Item {
keyboardActive = true;
ModalManager.openModal(modalHandle);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize(query || "", mode || "");
}
@@ -211,7 +210,6 @@ Item {
keyboardActive = false;
spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle);
closeCleanupTimer.start();
@@ -262,7 +260,7 @@ Item {
HyprlandFocusGrab {
id: focusGrab
windows: [launcherWindow]
active: false
active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: {
if (spotlightOpen) {
@@ -306,8 +304,9 @@ Item {
PanelWindow {
id: clickCatcher
screen: launcherWindow.screen
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
WlrLayershell.layer: root.effectiveLauncherLayer
@@ -373,24 +372,24 @@ Item {
WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors {
top: true
left: true
right: root.useBackgroundDarken
bottom: root.useBackgroundDarken
right: root.useSingleWindow
bottom: root.useSingleWindow
}
WlrLayershell.margins {
left: root.useBackgroundDarken ? 0 : root.windowX
top: root.useBackgroundDarken ? 0 : root.windowY
left: root.useSingleWindow ? 0 : root.windowX
top: root.useSingleWindow ? 0 : root.windowY
right: 0
bottom: 0
}
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
mask: Region {
item: launcherInputMask
@@ -400,15 +399,15 @@ Item {
id: launcherInputMask
visible: false
color: "transparent"
x: root.useBackgroundDarken ? 0 : modalContainer.x
y: root.useBackgroundDarken ? 0 : modalContainer.y
width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width
height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height
x: root.useSingleWindow ? 0 : modalContainer.x
y: root.useSingleWindow ? 0 : modalContainer.y
width: root.useSingleWindow ? launcherWindow.width : modalContainer.width
height: root.useSingleWindow ? launcherWindow.height : modalContainer.height
}
MouseArea {
anchors.fill: parent
enabled: root.useBackgroundDarken && spotlightOpen
enabled: root.useSingleWindow && spotlightOpen
z: -2
onClicked: root.hide()
}
@@ -432,13 +431,23 @@ Item {
Item {
id: modalContainer
x: root.useBackgroundDarken ? root.alignedX : root.contentX
y: root.useBackgroundDarken ? root.alignedY : root.contentY
x: root.useSingleWindow ? root.alignedX : root.contentX
y: root.useSingleWindow ? root.alignedY : root.contentY
width: root.alignedWidth
height: root.alignedHeight
visible: _renderActive
z: 0
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
property bool _renderActive: contentVisible
property real publishedScale: contentVisible ? 1 : 0.96
property real publishedOpacity: contentVisible ? 1 : 0
-6
View File
@@ -1,7 +1,6 @@
import QtQml
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -29,11 +28,6 @@ DankModal {
KeybindsService.loadCheatsheet();
}
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
function scrollDown() {
if (!root.activeFlickable)
return;
-7
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell
import qs.Common
@@ -45,12 +44,6 @@ DankModal {
}
}
HyprlandFocusGrab {
id: grab
windows: [muxModal.contentWindow]
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
}
function toggle() {
if (shouldBeVisible) {
hide();
-6
View File
@@ -1,5 +1,4 @@
import QtQuick
import Quickshell.Hyprland
import Quickshell.Io
import qs.Common
import qs.Modals.Common
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:notification-center-modal"
HyprlandFocusGrab {
windows: [notificationModal.contentWindow]
active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus
}
property bool notificationModalOpen: false
property var notificationListRef: null
property var historyListRef: null
-6
View File
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:power-menu"
keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property int selectedIndex: 0
property int selectedRow: 0
property int selectedCol: 0