Files
Marlon Kranzandpurian23 0444835016 fix(frame): make connected mode honor the settings it overrides (#3665)
* fix(frame): make connected mode honor the settings it overrides

Several frame-mode behaviors contradicted what the settings UI shows:

- Frame -> Blur gated blur for every shell surface in connected mode,
  including the Settings window, polkit, context menus, OSDs and toasts.
  WindowBlur now defaults to Background blur; only surfaces painted
  with the frame color (connected popouts, slideouts, Control Center)
  follow Frame -> Blur, and frame-owned surfaces are blurred by the
  frame as before.
- The notepad slideout painted its own fill at the notepad
  transparency. On screens where the frame owns connected chrome it now
  claims a frame surface (new connected_arc slot 5), so the frame paints
  it flush with the cutout, with fillets, and follows the slide.
- Notification popups painted a card surface on top of the frame body,
  stacking fills. That layer is transparent when the frame paints it.
- Connected mode stripped shadow, border and corner styling from every
  bar, including bars on displays without a frame, and hid the rows.
  Styling is now stripped and restored per bar based on whether it
  covers a frame display, re-checked on screen, frame display and
  display-name-mode changes.
- Auto popup gaps and Gap size were hidden in separate mode although
  they still apply there; they now hide only in connected mode.
- The launcher style picker was hidden in connected mode while Island
  still overrode the connected launcher. Connected mode now always uses
  the connected launcher; the picker shows Full, disabled, with a note
  linking to the Bar layout picker.
- The frame shadow ignored Shadows -> Apply to -> Bars.

* fix(frame): address review feedback on connected mode

- Skip bar style reconciliation while the screen list is empty, so a
  hotplug doesn't restore and re-strip every bar.
- Replace the 16 copies of the connector blur anchor and cutout in
  FrameWindow with a ConnectorBlurRegion inline component.
- Let connected mode use the island launcher again now that the island
  can sit in any bar. The picker offers Full and Island there and hides
  Spotlight, which opens the full launcher in that mode anyway; the note
  and the SettingsControlledBy bar target are dropped.

* frame: address review findings on connected mode

---------

Co-authored-by: purian23 <[email protected]>
2026-10-10 00:04:17 -04:00

919 lines
40 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
import "../../Common/FluidGeometry.js" as FluidGeometry
Item {
id: root
readonly property bool isFloatingWindowSurface: !frameOwnsConnectedChrome
readonly property var log: Log.scoped("DModalHost")
required property var modalHandle
property bool connected: true
property bool _fluidMotionActive: false
readonly property rect fluidTarget: Qt.rect(0, 0, alignedWidth, alignedHeight)
readonly property string fluidSide: frameOwnsConnectedChrome ? resolvedConnectedBarSide : ""
readonly property rect fluidCollapsedRect: !_fluidMotionActive ? fluidTarget : FluidGeometry.collapsed(fluidTarget, fluidSide, fluidSide === "left" || fluidSide === "right" ? alignedHeight / 2 : alignedWidth / 2, Theme.barHeight)
readonly property rect fluidBody: _fluidMotionActive ? FluidGeometry.limitOvershoot(FluidGeometry.interpolate(fluidCollapsedRect, fluidTarget, morph.value), fluidTarget, Theme.fluidOvershootLimit) : fluidTarget
property string layerNamespace: modalHandle.layerNamespace
property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader
property Item directContent: modalHandle.directContent
property real modalWidth: modalHandle.modalWidth
property real modalHeight: modalHandle.modalHeight
property var targetScreen: modalHandle.targetScreen
readonly property var effectiveScreen: contentWindow.screen ?? targetScreen
readonly property real screenWidth: effectiveScreen?.width ?? 1920
readonly property real screenHeight: effectiveScreen?.height ?? 1080
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
property bool showBackground: modalHandle.showBackground
property real backgroundOpacity: modalHandle.backgroundOpacity
property string positioning: modalHandle.positioning
property point customPosition: modalHandle.customPosition
property bool closeOnEscapeKey: modalHandle.closeOnEscapeKey
property bool closeOnBackgroundClick: modalHandle.closeOnBackgroundClick
property string animationType: modalHandle.animationType
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
readonly property bool frameConnectedMode: connected && FrameTransitionState.effectiveFrameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
property bool _overStackedModal: false
readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" && !allowStacking && !_overStackedModal && effectiveModalLayer === WlrLayer.Top && CompositorService.canShareConnectedFrameChromeForScreen(effectiveScreen)
function _dockOccupiesSide(side) {
return SettingsData.dockOccupiesSide(root.effectiveScreen, side);
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
property int animationDuration: modalHandle.animationDuration
property real animationScaleCollapsed: modalHandle.animationScaleCollapsed
property real animationOffset: modalHandle.animationOffset
property list<real> animationEnterCurve: modalHandle.animationEnterCurve
property list<real> animationExitCurve: modalHandle.animationExitCurve
property color backgroundColor: modalHandle.backgroundColor
property color borderColor: modalHandle.borderColor
property real borderWidth: modalHandle.borderWidth
property real cornerRadius: modalHandle.cornerRadius
readonly property color effectiveBackgroundColor: frameOwnsConnectedChrome ? Theme.connectedSurfaceColor : backgroundColor
readonly property color effectiveBorderColor: frameOwnsConnectedChrome ? Theme.withAlpha(borderColor, 0) : borderColor
readonly property real effectiveBorderWidth: frameOwnsConnectedChrome ? 0 : borderWidth
readonly property real effectiveCornerRadius: frameOwnsConnectedChrome ? Theme.connectedSurfaceRadius : cornerRadius
property bool enableShadow: modalHandle.enableShadow
property alias modalFocusScope: focusScope
property Item handleFocusScope: null
property bool shouldBeVisible: false
property bool isClosing: false
property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: modalHandle.allowFocusOverride
property bool allowStacking: modalHandle.allowStacking
property bool keepContentLoaded: modalHandle.keepContentLoaded
property bool keepPopoutsOpen: modalHandle.keepPopoutsOpen
property var customKeyboardFocus: modalHandle.customKeyboardFocus
property bool useOverlayLayer: modalHandle.useOverlayLayer
readonly property var effectiveModalLayer: root.useOverlayLayer ? WlrLayer.Overlay : LayerShell.fromEnv("DMS_MODAL_LAYER", WlrLayer.Top, {
"allow": ["top", "overlay"],
"invalidLayer": WlrLayer.Top,
"label": "modals",
"error": true
})
property real frozenMotionOffsetX: 0
property real frozenMotionOffsetY: 0
readonly property alias contentWindow: contentWindow
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: !connected && showBackground && !FrameTransitionState.effectiveFrameEnabled && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: connected || CompositorService.isHyprland || useBackground
signal opened
signal dialogClosed
signal backgroundClicked
Timer {
id: _syncTimer
interval: 0
onTriggered: root._flushSync()
}
property bool animationsEnabled: true
property bool _presentPending: false
property bool _surfaceFrameReady: false
property bool _fullSyncPending: false
function _currentScreenName() {
return effectiveScreen ? effectiveScreen.name : "";
}
Loader {
id: modalChrome
active: root.connected
sourceComponent: ConnectedModalChrome {
modalHandle: root.modalHandle
claimPrefix: root.layerNamespace + ":modal"
surfaceKind: "modal"
screenName: root._currentScreenName()
enabled: root.frameOwnsConnectedChrome
active: root.shouldBeVisible
presented: root.shouldBeVisible || contentWindow.visible
dockBlocked: root._dockBlocksEmergence
dockSide: root.resolvedConnectedBarSide
onRecoveryRequested: root._queueFullSync()
}
}
function _publishModalChromeState() {
if (!connected)
return false;
const presented = shouldBeVisible || contentWindow.visible;
const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open"));
const bodyRect = {
"x": alignedX + fluidBody.x,
"y": alignedY + fluidBody.y,
"width": fluidBody.width,
"height": fluidBody.height
};
const animationOffset = {
"x": modalContainer.animX,
"y": modalContainer.animY
};
const state = {
"kind": "modal",
"screenName": root._currentScreenName(),
"phase": phase,
"visible": presented,
"presented": presented,
"layer": root.effectiveModalLayer === WlrLayer.Overlay ? "overlay" : "top",
"barSide": resolvedConnectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": bodyRect.x,
"bodyY": bodyRect.y,
"bodyW": bodyRect.width,
"bodyH": bodyRect.height,
"animX": animationOffset.x,
"animY": animationOffset.y,
"omitStartConnector": false,
"omitEndConnector": false,
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
};
return modalChrome.item?.publish(state) ?? false;
}
function _queueFullSync() {
if (!connected)
return;
_fullSyncPending = true;
if (!_syncTimer.running)
_syncTimer.restart();
}
function _flushSync() {
if (!_fullSyncPending)
return;
_fullSyncPending = false;
_publishModalChromeState();
}
function _syncModalAnim() {
if (!frameOwnsConnectedChrome)
return;
modalChrome.item?.updateAnim(modalContainer.animX, modalContainer.animY);
}
function _syncModalBody() {
if (!frameOwnsConnectedChrome)
return;
modalChrome.item?.updateBody(alignedX + fluidBody.x, alignedY + fluidBody.y, fluidBody.width, fluidBody.height);
}
function _releaseModalChrome() {
if (!connected)
return;
modalChrome.item?.release();
}
function _kickBlurCommit() {
if (typeof contentWindow.update === "function")
contentWindow.update();
}
function _kickBlurCommitUnlessConnected() {
if (connected)
return;
_kickBlurCommit();
}
// Low resource scalar writes: publish synchronously to stay in the same frame
function _syncAlignedGeometry() {
if (connected) {
_syncModalBody();
return;
}
_kickBlurCommit();
}
onShouldBeVisibleChanged: {
morph.retarget(shouldBeVisible ? 1 : 0);
if (connected) {
_queueFullSync();
return;
}
_kickBlurCommit();
}
onFrameOwnsConnectedChromeChanged: {
if (!connected)
return;
_publishModalChromeState();
}
onResolvedConnectedBarSideChanged: {
if (!connected)
return;
_queueFullSync();
}
onFluidBodyChanged: {
if (!connected || !_fluidMotionActive)
return;
_syncModalBody();
}
onAlignedXChanged: _syncAlignedGeometry()
onAlignedYChanged: _syncAlignedGeometry()
onAlignedWidthChanged: _syncAlignedGeometry()
onAlignedHeightChanged: _syncAlignedGeometry()
function open() {
if (!contentWindow.visible)
_fluidMotionActive = Theme.isFluidEffect;
closeTimer.stop();
isClosing = false;
if (connected) {
_openConnected();
return;
}
_openStandalone();
}
function _openConnected() {
if (!_fluidMotionActive || !contentWindow.visible)
animationsEnabled = false;
const focusedScreen = root.targetScreen ?? CompositorService.getFocusedScreen();
if (!contentWindow.visible)
_overStackedModal = ModalManager.hasStackedModal((focusedScreen ?? effectiveScreen)?.name ?? "");
frozenMotionOffsetX = modalContainer.offsetX;
frozenMotionOffsetY = modalContainer.offsetY;
if (focusedScreen)
contentWindow.screen = focusedScreen;
ModalManager.openModal(modalHandle);
if (Theme.isDirectionalEffect)
contentWindow.visible = true;
Qt.callLater(() => {
root.animationsEnabled = true;
root._present();
});
}
function _openStandalone() {
const focusedScreen = root.targetScreen ?? CompositorService.getFocusedScreen();
const screenChanged = focusedScreen && contentWindow.screen !== focusedScreen;
if (focusedScreen) {
if (screenChanged)
contentWindow.visible = false;
contentWindow.screen = focusedScreen;
}
if (screenChanged) {
Qt.callLater(() => root._finishStandaloneOpen());
return;
}
_finishStandaloneOpen();
}
function _finishStandaloneOpen() {
ModalManager.openModal(modalHandle);
_present();
}
function _present() {
if (connected) {
_reveal();
return;
}
if (clickCatcher.item)
clickCatcher.item.present();
contentWindow.visible = true;
if (!_surfaceFrameReady) {
_presentPending = true;
return;
}
_reveal();
}
function _reveal() {
_presentPending = false;
shouldBeVisible = true;
if (clickCatcher.item)
clickCatcher.item.present();
contentWindow.visible = true;
opened();
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
}
on_SurfaceFrameReadyChanged: {
if (_surfaceFrameReady && _presentPending)
_reveal();
}
function close() {
if (_presentPending || (_fluidMotionActive && (SettingsData.reduceMotion || animationDuration <= 0))) {
instantClose();
return;
}
frozenMotionOffsetX = modalContainer.offsetX;
frozenMotionOffsetY = modalContainer.offsetY;
isClosing = !connected;
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(modalHandle);
closeTimer.restart();
}
function instantClose() {
_presentPending = false;
animationsEnabled = false;
isClosing = false;
shouldBeVisible = false;
shouldHaveFocus = false;
ModalManager.closeModal(modalHandle);
closeTimer.stop();
contentWindow.visible = false;
_fluidMotionActive = false;
dialogClosed();
Qt.callLater(() => animationsEnabled = true);
}
function toggle() {
shouldBeVisible ? close() : open();
}
Connections {
target: ModalManager
function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== modalHandle && !allowStacking && shouldBeVisible)
close();
}
}
readonly property var quickshellScreens: Quickshell.screens
onQuickshellScreensChanged: {
if (!contentWindow.screen)
return;
const currentScreenName = contentWindow.screen.name;
let screenStillExists = false;
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === currentScreenName) {
screenStillExists = true;
break;
}
}
if (screenStillExists) {
if (shouldBeVisible)
_queueFullSync();
return;
}
_releaseModalChrome();
const newScreen = CompositorService.getFocusedScreen();
if (newScreen) {
contentWindow.screen = newScreen;
}
}
Timer {
id: closeTimer
interval: root.connected ? Math.max(Theme.variantCloseInterval(root.animationDuration), morph.settleDurationMs + 32) : Math.max(root.animationDuration + 50, morph.settleDurationMs + 50)
onTriggered: {
if (root.shouldBeVisible)
return;
root.isClosing = false;
contentWindow.visible = false;
morph.snapTo(0);
root._fluidMotionActive = false;
root.dialogClosed();
}
}
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: Theme.spacingS
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 shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom":
{
const insetL = SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
const insetR = SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - alignedWidth) / 2);
}
case "left":
return SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
case "right":
return screenWidth - alignedWidth - SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
}
return 0;
}
readonly property real _connectedAlignedY: {
switch (resolvedConnectedBarSide) {
case "top":
return SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
case "bottom":
return screenHeight - alignedHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
case "left":
case "right":
{
const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return insetT + Math.max(0, (usable - alignedHeight) / 2);
}
}
return 0;
}
readonly property real alignedX: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedX : (() => {
switch (positioning) {
case "center":
return (screenWidth - alignedWidth) / 2;
case "top-right":
return Math.max(Theme.spacingL, screenWidth - alignedWidth - Theme.spacingL);
case "custom":
return customPosition.x;
default:
return 0;
}
})(), dpr)
readonly property real alignedY: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedY : (() => {
switch (positioning) {
case "center":
return (screenHeight - alignedHeight) / 2;
case "top-right":
return Theme.barHeight + Theme.spacingXS;
case "custom":
return customPosition.y;
default:
return 0;
}
})(), dpr)
Loader {
id: clickCatcher
active: !root.connected
sourceComponent: Item {
id: catcher
function present() {
if (!root.useSingleWindow)
catcherWindow.visible = true;
}
readonly property bool contentWindowVisible: contentWindow.visible
onContentWindowVisibleChanged: {
if (!contentWindowVisible && !root.useSingleWindow)
catcherWindow.visible = false;
}
PanelWindow {
id: catcherWindow
screen: contentWindow.screen
visible: false
color: "transparent"
updatesEnabled: false
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.closeOnBackgroundClick && root.shouldBeVisible
onClicked: root.backgroundClicked()
}
}
}
}
PanelWindow {
id: contentWindow
visible: false
color: "transparent"
WindowBlur {
targetWindow: contentWindow
blurEnabled: !root.frameOwnsConnectedChrome
surfaceColor: root.effectiveBackgroundColor
readonly property real s: Math.min(1, modalContainer.scaleValue)
readonly property real op: Math.max(0, Math.min(1, (morph.value - 0.06) * 2))
readonly property real visibleScale: root.connected ? s : s * op
blurX: root._fluidMotionActive ? (modalReveal.x + modalContainer.x + root.fluidBody.x) : modalReveal.x + modalContainer.x + modalContainer.width * (1 - visibleScale) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
blurY: root._fluidMotionActive ? (modalReveal.y + modalContainer.y + root.fluidBody.y) : modalReveal.y + modalContainer.y + modalContainer.height * (1 - visibleScale) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: root._fluidMotionActive ? (root.shouldBeVisible ? root.fluidBody.width : 0) : (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * visibleScale : 0
blurHeight: root._fluidMotionActive ? (root.shouldBeVisible ? root.fluidBody.height : 0) : (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * visibleScale : 0
blurRadius: root.effectiveCornerRadius
}
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: root.effectiveModalLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.shouldHaveFocus, root.customKeyboardFocus)
anchors {
left: true
top: true
right: root.useSingleWindow
bottom: root.useSingleWindow
}
WlrLayershell.margins {
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - root.shadowBuffer, root.dpr))
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - root.shadowBuffer, root.dpr))
right: 0
bottom: 0
}
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (root.shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (root.shadowBuffer * 2)
onVisibleChanged: {
if (!visible)
root._surfaceFrameReady = false;
if (!visible && Qt.inputMethod) {
Qt.inputMethod.hide();
Qt.inputMethod.reset();
}
if (!root.connected)
return;
if (visible)
root._publishModalChromeState();
else
root._releaseModalChrome();
}
Connections {
target: modalContainer.Window.window
enabled: contentWindow.visible && !root._surfaceFrameReady
function onFrameSwapped() {
root._surfaceFrameReady = true;
}
}
MouseArea {
anchors.fill: parent
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
z: -2
onClicked: root.backgroundClicked()
}
Rectangle {
anchors.fill: parent
z: -1
color: "black"
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.useBackground
Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation {
easing.type: Easing.BezierSpline
duration: root.animationDuration
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
Item {
id: modalReveal
// Clip to final footprint while frame-owned chrome grows from the bar edge.
x: root.useSingleWindow ? root.alignedX : root.shadowBuffer
y: root.useSingleWindow ? root.alignedY : root.shadowBuffer
width: root.alignedWidth
height: root.alignedHeight
clip: root.frameOwnsConnectedChrome
Item {
id: modalContainer
readonly property bool isFloatingWindowSurface: root.isFloatingWindowSurface
x: root.connected ? Theme.snap(animX, root.dpr) : 0
y: root.connected ? Theme.snap(animY, root.dpr) : 0
width: root.alignedWidth
height: root.alignedHeight
MouseArea {
anchors.fill: parent
enabled: root.useSingleWindow && root.shouldBeVisible
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse => mouse.accepted = true
onClicked: mouse => 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.connected)
return slide ? 15 : 0;
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.connected)
return slide ? -30 : root.animationOffset;
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;
}
// openProgress spring: 0 = collapsed, 1 = open.
SpringMotion {
id: morph
enabled: root.animationsEnabled
onValueChanged: root._kickBlurCommitUnlessConnected()
reducedMotion: root.animationDuration <= 0
positionEpsilon: Math.max(0.001, 0.25 / root.dpr / modalContainer.morphTravelPx)
velocityEpsilon: positionEpsilon * damping / Math.max(0.001, 2 * mass)
stiffness: Theme.springPreset("default", root.animationDuration).stiffness
damping: Theme.springPreset("default", root.animationDuration).damping
Component.onCompleted: snapTo(root.shouldBeVisible ? 1 : 0)
}
readonly property real animX: root._fluidMotionActive ? 0 : (root.connected ? root.frozenMotionOffsetX : offsetX) * (1 - morph.value)
readonly property real animY: root._fluidMotionActive ? 0 : (root.connected ? root.frozenMotionOffsetY : offsetY) * (1 - morph.value)
onAnimXChanged: {
if (!root.connected)
return;
root._syncModalAnim();
}
onAnimYChanged: {
if (!root.connected)
return;
root._syncModalAnim();
}
readonly property real scaleValue: root._fluidMotionActive ? 1 : root.animationScaleCollapsed + (1.0 - root.animationScaleCollapsed) * morph.value
readonly property real morphTravelPx: Math.max(1, Math.abs(offsetX), Math.abs(root.frozenMotionOffsetX), Math.abs(offsetY), Math.abs(root.frozenMotionOffsetY), (1 - root.animationScaleCollapsed) * Math.max(root.alignedWidth, root.alignedHeight), root._fluidMotionActive ? Math.max(root.alignedWidth, root.alignedHeight) : 0)
SurfaceContentClip {
id: contentClip
active: root._fluidMotionActive
body: root.fluidBody
targetRect: root.fluidTarget
Item {
id: animatedContent
width: root.alignedWidth
height: root.alignedHeight
x: root.connected ? 0 : Theme.snap(modalContainer.animX, root.dpr)
y: root.connected ? 0 : Theme.snap(modalContainer.animY, root.dpr)
clip: false
opacity: root._fluidMotionActive || (root.connected && Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
scale: modalContainer.scaleValue
Behavior on opacity {
enabled: root.animationsEnabled && !root._fluidMotionActive && (!root.connected || !Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: root.connected ? Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale) : root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
ElevationShadow {
parent: root._fluidMotionActive ? modalContainer : animatedContent
anchors.fill: root._fluidMotionActive ? undefined : parent
x: root._fluidMotionActive ? root.fluidBody.x : 0
y: root._fluidMotionActive ? root.fluidBody.y : 0
width: root._fluidMotionActive ? root.fluidBody.width : animatedContent.width
height: root._fluidMotionActive ? root.fluidBody.height : animatedContent.height
z: root._fluidMotionActive ? -1 : 0
opacity: root._fluidMotionActive ? FluidGeometry.chromeOpacity(morph.value) : 1
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius
targetColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.effectiveBackgroundColor, 0) : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? Theme.withAlpha(root.effectiveBorderColor, 0) : 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 {
parent: root._fluidMotionActive ? modalContainer : animatedContent
anchors.fill: root._fluidMotionActive ? undefined : parent
x: root._fluidMotionActive ? root.fluidBody.x : 0
y: root._fluidMotionActive ? root.fluidBody.y : 0
width: root._fluidMotionActive ? root.fluidBody.width : animatedContent.width
height: root._fluidMotionActive ? root.fluidBody.height : animatedContent.height
opacity: root._fluidMotionActive ? FluidGeometry.chromeOpacity(morph.value) : 1
radius: root.effectiveCornerRadius
color: "transparent"
border.color: root.frameOwnsConnectedChrome ? Theme.withAlpha(BlurService.borderColor, 0) : BlurService.borderColor
border.width: root.frameOwnsConnectedChrome ? 0 : BlurService.borderWidth
z: 100
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
clip: false
Keys.forwardTo: root.handleFocusScope ? [root.handleFocusScope, focusScope] : [focusScope]
Item {
id: directContentWrapper
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
readonly property Item rootDirectContent: root.directContent
onRootDirectContentChanged: {
if (rootDirectContent) {
rootDirectContent.parent = directContentWrapper;
rootDirectContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
}
Loader {
id: contentLoader
sourceComponent: root.modalHandle.content
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());
}
}
}
}
}
}
}
}
FocusScope {
id: focusScope
objectName: "modalFocusScope"
anchors.fill: parent
visible: root.shouldBeVisible || contentWindow.visible
focus: root.shouldBeVisible
Keys.onEscapePressed: event => {
if (root.closeOnEscapeKey && root.shouldHaveFocus) {
root.close();
event.accepted = true;
}
}
}
}
}