1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-17 11:12:06 -04:00

(frameMode): New Modal & Launcher connections

This commit is contained in:
purian23
2026-04-17 00:30:35 -04:00
parent 745570bc8b
commit f88cc45e0d
10 changed files with 991 additions and 32 deletions

View File

@@ -278,4 +278,193 @@ Singleton {
notificationStates = next; notificationStates = next;
return true; return true;
} }
// DankModal / DankLauncherV2Modal State
readonly property var emptyModalState: ({
"visible": false,
"barSide": "bottom",
"bodyX": 0,
"bodyY": 0,
"bodyW": 0,
"bodyH": 0,
"animX": 0,
"animY": 0,
"omitStartConnector": false,
"omitEndConnector": false
})
property var modalStates: ({})
function _cloneModalStates() {
const next = {};
for (const screenName in modalStates)
next[screenName] = modalStates[screenName];
return next;
}
function _normalizeModalState(state) {
return {
"visible": !!(state && state.visible),
"barSide": state && state.barSide ? state.barSide : "bottom",
"bodyX": Number(state && state.bodyX !== undefined ? state.bodyX : 0),
"bodyY": Number(state && state.bodyY !== undefined ? state.bodyY : 0),
"bodyW": Number(state && state.bodyW !== undefined ? state.bodyW : 0),
"bodyH": Number(state && state.bodyH !== undefined ? state.bodyH : 0),
"animX": Number(state && state.animX !== undefined ? state.animX : 0),
"animY": Number(state && state.animY !== undefined ? state.animY : 0),
"omitStartConnector": !!(state && state.omitStartConnector),
"omitEndConnector": !!(state && state.omitEndConnector)
};
}
function _sameModalGeometry(a, b) {
if (!a || !b)
return false;
return Math.abs(Number(a.bodyX) - Number(b.bodyX)) < 0.5
&& Math.abs(Number(a.bodyY) - Number(b.bodyY)) < 0.5
&& Math.abs(Number(a.bodyW) - Number(b.bodyW)) < 0.5
&& Math.abs(Number(a.bodyH) - Number(b.bodyH)) < 0.5
&& Math.abs(Number(a.animX) - Number(b.animX)) < 0.5
&& Math.abs(Number(a.animY) - Number(b.animY)) < 0.5;
}
function _sameModalState(a, b) {
if (!a || !b)
return false;
return a.visible === b.visible
&& a.barSide === b.barSide
&& a.omitStartConnector === b.omitStartConnector
&& a.omitEndConnector === b.omitEndConnector
&& _sameModalGeometry(a, b);
}
function setModalState(screenName, state) {
if (!screenName || !state)
return false;
const normalized = _normalizeModalState(state);
if (_sameModalState(modalStates[screenName], normalized))
return true;
const next = _cloneModalStates();
next[screenName] = normalized;
modalStates = next;
return true;
}
function clearModalState(screenName) {
if (!screenName || !modalStates[screenName])
return false;
const next = _cloneModalStates();
delete next[screenName];
modalStates = next;
return true;
}
function setModalAnim(screenName, animX, animY) {
if (!screenName)
return false;
const cur = modalStates[screenName];
if (!cur)
return false;
let changed = false;
const nextAnimX = animX !== undefined ? Number(animX) : cur.animX;
const nextAnimY = animY !== undefined ? Number(animY) : cur.animY;
if (Math.abs(nextAnimX - cur.animX) >= 0.5 || Math.abs(nextAnimY - cur.animY) >= 0.5) {
const updated = {
"visible": cur.visible,
"barSide": cur.barSide,
"bodyX": cur.bodyX,
"bodyY": cur.bodyY,
"bodyW": cur.bodyW,
"bodyH": cur.bodyH,
"animX": nextAnimX,
"animY": nextAnimY,
"omitStartConnector": cur.omitStartConnector,
"omitEndConnector": cur.omitEndConnector
};
const next = _cloneModalStates();
next[screenName] = updated;
modalStates = next;
changed = true;
}
return changed;
}
function setModalBody(screenName, bodyX, bodyY, bodyW, bodyH) {
if (!screenName)
return false;
const cur = modalStates[screenName];
if (!cur)
return false;
const nx = bodyX !== undefined ? Number(bodyX) : cur.bodyX;
const ny = bodyY !== undefined ? Number(bodyY) : cur.bodyY;
const nw = bodyW !== undefined ? Number(bodyW) : cur.bodyW;
const nh = bodyH !== undefined ? Number(bodyH) : cur.bodyH;
if (Math.abs(nx - cur.bodyX) < 0.5
&& Math.abs(ny - cur.bodyY) < 0.5
&& Math.abs(nw - cur.bodyW) < 0.5
&& Math.abs(nh - cur.bodyH) < 0.5)
return false;
const updated = {
"visible": cur.visible,
"barSide": cur.barSide,
"bodyX": nx,
"bodyY": ny,
"bodyW": nw,
"bodyH": nh,
"animX": cur.animX,
"animY": cur.animY,
"omitStartConnector": cur.omitStartConnector,
"omitEndConnector": cur.omitEndConnector
};
const next = _cloneModalStates();
next[screenName] = updated;
modalStates = next;
return true;
}
// ─── Dock retract coordination ────────────────────────────────
property var dockRetractRequests: ({})
function _cloneRetractRequests() {
const next = {};
for (const k in dockRetractRequests)
next[k] = dockRetractRequests[k];
return next;
}
function requestDockRetract(requesterId, screenName, side) {
if (!requesterId || !screenName || !side)
return false;
const existing = dockRetractRequests[requesterId];
if (existing && existing.screenName === screenName && existing.side === side)
return true;
const next = _cloneRetractRequests();
next[requesterId] = { "screenName": screenName, "side": side };
dockRetractRequests = next;
return true;
}
function releaseDockRetract(requesterId) {
if (!requesterId || !dockRetractRequests[requesterId])
return false;
const next = _cloneRetractRequests();
delete next[requesterId];
dockRetractRequests = next;
return true;
}
function dockRetractActiveForSide(screenName, side) {
if (!screenName || !side)
return false;
for (const k in dockRetractRequests) {
const r = dockRetractRequests[k];
if (r && r.screenName === screenName && r.side === side)
return true;
}
return false;
}
} }

View File

@@ -237,6 +237,9 @@ Singleton {
onFrameBlurEnabledChanged: saveSettings() onFrameBlurEnabledChanged: saveSettings()
property bool frameCloseGaps: false property bool frameCloseGaps: false
onFrameCloseGapsChanged: saveSettings() onFrameCloseGapsChanged: saveSettings()
property string frameLauncherEmergeSide: "bottom"
onFrameLauncherEmergeSideChanged: saveSettings()
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
property int previousDirectionalMode: 1 property int previousDirectionalMode: 1
onPreviousDirectionalModeChanged: saveSettings() onPreviousDirectionalModeChanged: saveSettings()
property var connectedFrameBarStyleBackups: ({}) property var connectedFrameBarStyleBackups: ({})
@@ -2206,6 +2209,12 @@ Singleton {
return edges; return edges;
} }
function frameEdgeInsetForSide(screen, side) {
if (!frameEnabled || !screen) return 0;
const edges = getActiveBarEdgesForScreen(screen);
return edges.includes(side) ? frameBarSize : frameThickness;
}
function getActiveBarThicknessForScreen(screen) { function getActiveBarThicknessForScreen(screen) {
if (frameEnabled) return frameBarSize; if (frameEnabled) return frameBarSize;
if (!screen) return frameThickness; if (!screen) return frameThickness;

View File

@@ -553,7 +553,8 @@ var SPEC = {
frameBarSize: { def: 40 }, frameBarSize: { def: 40 },
frameShowOnOverview: { def: false }, frameShowOnOverview: { def: false },
frameBlurEnabled: { def: true }, frameBlurEnabled: { def: true },
frameCloseGaps: { def: false } frameCloseGaps: { def: false },
frameLauncherEmergeSide: { def: "bottom" }
}; };
function getValidKeys() { function getValidKeys() {

View File

@@ -26,6 +26,33 @@ Item {
property bool closeOnEscapeKey: true property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true property bool closeOnBackgroundClick: true
property string animationType: "scale" 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)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== ""
function _dockOccupiesSide(side) {
if (!SettingsData.showDock) return false;
switch (side) {
case "top": return SettingsData.dockPosition === SettingsData.Position.Top;
case "bottom": return SettingsData.dockPosition === SettingsData.Position.Bottom;
case "left": return SettingsData.dockPosition === SettingsData.Position.Left;
case "right": return SettingsData.dockPosition === SettingsData.Position.Right;
}
return false;
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome
&& _dockOccupiesSide(resolvedConnectedBarSide)
readonly property bool connectedMotionParity: Theme.isConnectedEffect readonly property bool connectedMotionParity: Theme.isConnectedEffect
property int animationDuration: connectedMotionParity ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration property int animationDuration: connectedMotionParity ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
property real animationScaleCollapsed: Theme.effectScaleCollapsed property real animationScaleCollapsed: Theme.effectScaleCollapsed
@@ -66,6 +93,107 @@ Item {
property bool animationsEnabled: true property bool animationsEnabled: true
// ─── Connected chrome sync ────────────────────────────────────────────────
property string _chromeClaimId: ""
property bool _fullSyncPending: false
function _nextChromeClaimId() {
return layerNamespace + ":modal:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
}
function _currentScreenName() {
return effectiveScreen ? effectiveScreen.name : "";
}
function _publishModalChromeState() {
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalState(screenName, {
"visible": shouldBeVisible || contentWindow.visible,
"barSide": resolvedConnectedBarSide,
"bodyX": alignedX,
"bodyY": alignedY,
"bodyW": alignedWidth,
"bodyH": alignedHeight,
"animX": modalContainer ? modalContainer.animX : 0,
"animY": modalContainer ? modalContainer.animY : 0,
"omitStartConnector": false,
"omitEndConnector": false
});
}
function _syncModalChromeState() {
if (!frameOwnsConnectedChrome) {
_releaseModalChrome();
return;
}
if (!_chromeClaimId)
_chromeClaimId = _nextChromeClaimId();
_publishModalChromeState();
if (_dockBlocksEmergence && (shouldBeVisible || contentWindow.visible))
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
else
ConnectedModeState.releaseDockRetract(_chromeClaimId);
}
function _flushFullSync() {
_fullSyncPending = false;
_syncModalChromeState();
}
function _queueFullSync() {
if (_fullSyncPending) return;
_fullSyncPending = true;
Qt.callLater(() => {
if (root && typeof root._flushFullSync === "function")
root._flushFullSync();
});
}
function _syncModalAnim() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName || !modalContainer) return;
ConnectedModeState.setModalAnim(screenName, modalContainer.animX, modalContainer.animY);
}
function _syncModalBody() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight);
}
function _releaseModalChrome() {
if (_chromeClaimId) {
ConnectedModeState.releaseDockRetract(_chromeClaimId);
_chromeClaimId = "";
}
const screenName = _currentScreenName();
if (screenName)
ConnectedModeState.clearModalState(screenName);
}
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
onResolvedConnectedBarSideChanged: _queueFullSync()
onShouldBeVisibleChanged: _queueFullSync()
onAlignedXChanged: _syncModalBody()
onAlignedYChanged: _syncModalBody()
onAlignedWidthChanged: _syncModalBody()
onAlignedHeightChanged: _syncModalBody()
Component.onDestruction: _releaseModalChrome()
Connections {
target: contentWindow
function onVisibleChanged() {
if (contentWindow.visible)
root._syncModalChromeState();
else
root._releaseModalChrome();
}
}
function open() { function open() {
closeTimer.stop(); closeTimer.stop();
animationsEnabled = false; animationsEnabled = false;
@@ -167,9 +295,11 @@ Item {
} }
} }
// shadowRenderPadding is zeroed when frame owns the chrome
// Wayland then clips any content translating past
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: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: { readonly property real shadowMotionPadding: {
if (Theme.isConnectedEffect) if (Theme.isConnectedEffect)
return 0; return 0;
@@ -187,7 +317,47 @@ Item {
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)
readonly property real alignedX: Theme.snap((() => { function _frameEdgeInset(side) {
if (!effectiveScreen) return 0;
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom": {
const insetL = _frameEdgeInset("left");
const insetR = _frameEdgeInset("right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - alignedWidth) / 2);
}
case "left":
return _frameEdgeInset("left");
case "right":
return screenWidth - alignedWidth - _frameEdgeInset("right");
}
return 0;
}
readonly property real _connectedAlignedY: {
switch (resolvedConnectedBarSide) {
case "top":
return _frameEdgeInset("top");
case "bottom":
return screenHeight - alignedHeight - _frameEdgeInset("bottom");
case "left":
case "right": {
const insetT = _frameEdgeInset("top");
const insetB = _frameEdgeInset("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) { switch (positioning) {
case "center": case "center":
return (screenWidth - alignedWidth) / 2; return (screenWidth - alignedWidth) / 2;
@@ -200,7 +370,7 @@ Item {
} }
})(), dpr) })(), dpr)
readonly property real alignedY: Theme.snap((() => { readonly property real alignedY: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedY : (() => {
switch (positioning) { switch (positioning) {
case "center": case "center":
return (screenHeight - alignedHeight) / 2; return (screenHeight - alignedHeight) / 2;
@@ -271,12 +441,12 @@ Item {
WindowBlur { WindowBlur {
targetWindow: contentWindow targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, modalContainer.scaleValue) readonly property real s: Math.min(1, modalContainer.scaleValue)
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr) 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) blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0 blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0 blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
blurRadius: root.effectiveCornerRadius blurRadius: root.effectiveCornerRadius
} }
@@ -392,7 +562,17 @@ Item {
readonly property real customDistRight: root.screenWidth - customAnchorX readonly property real customDistRight: root.screenWidth - customAnchorX
readonly property real customDistTop: customAnchorY readonly property real customDistTop: customAnchorY
readonly property real customDistBottom: root.screenHeight - 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: { readonly property real offsetX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left": return -connectedEmergenceTravelX;
case "right": return connectedEmergenceTravelX;
}
return 0;
}
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0; return 0;
if (slide && !directionalEffect && !depthEffect) if (slide && !directionalEffect && !depthEffect)
@@ -428,6 +608,13 @@ Item {
return 0; return 0;
} }
readonly property real offsetY: { readonly property real offsetY: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top": return -connectedEmergenceTravelY;
case "bottom": return connectedEmergenceTravelY;
}
return 0;
}
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0; return 0;
if (slide && !directionalEffect && !depthEffect) if (slide && !directionalEffect && !depthEffect)
@@ -467,6 +654,9 @@ Item {
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY
onAnimXChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
onAnimYChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed
@@ -527,18 +717,18 @@ Item {
level: root.shadowLevel level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius targetRadius: root.effectiveCornerRadius
targetColor: root.effectiveBackgroundColor targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
borderColor: root.effectiveBorderColor borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.effectiveBorderWidth borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
} }
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: root.effectiveCornerRadius radius: root.effectiveCornerRadius
color: "transparent" color: "transparent"
border.color: root.connectedSurfaceOverride ? "transparent" : BlurService.borderColor border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
border.width: root.connectedSurfaceOverride ? 0 : BlurService.borderWidth border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
z: 100 z: 100
} }

View File

@@ -63,8 +63,72 @@ Item {
} }
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100) readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100)
readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100) readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100)
readonly property real modalX: (screenWidth - modalWidth) / 2
readonly property real modalY: (screenHeight - modalHeight) / 2 readonly property string preferredConnectedBarSide: SettingsData.frameLauncherEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled
&& Theme.isConnectedEffect
&& !!effectiveScreen
&& SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== ""
function _dockOccupiesSide(side) {
if (!SettingsData.showDock) return false;
switch (side) {
case "top": return SettingsData.dockPosition === SettingsData.Position.Top;
case "bottom": return SettingsData.dockPosition === SettingsData.Position.Bottom;
case "left": return SettingsData.dockPosition === SettingsData.Position.Left;
case "right": return SettingsData.dockPosition === SettingsData.Position.Right;
}
return false;
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
function _frameEdgeInset(side) {
if (!effectiveScreen) return 0;
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedModalX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom": {
const insetL = _frameEdgeInset("left");
const insetR = _frameEdgeInset("right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - modalWidth) / 2);
}
case "left":
return _frameEdgeInset("left");
case "right":
return screenWidth - modalWidth - _frameEdgeInset("right");
}
return (screenWidth - modalWidth) / 2;
}
readonly property real _connectedModalY: {
switch (resolvedConnectedBarSide) {
case "top":
return _frameEdgeInset("top");
case "bottom":
return screenHeight - modalHeight - _frameEdgeInset("bottom");
case "left":
case "right": {
const insetT = _frameEdgeInset("top");
const insetB = _frameEdgeInset("bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return insetT + Math.max(0, (usable - modalHeight) / 2);
}
}
return (screenHeight - modalHeight) / 2;
}
readonly property real modalX: frameOwnsConnectedChrome ? _connectedModalX : ((screenWidth - modalWidth) / 2)
readonly property real modalY: frameOwnsConnectedChrome ? _connectedModalY : ((screenHeight - modalHeight) / 2)
readonly property bool connectedSurfaceOverride: Theme.isConnectedEffect readonly property bool connectedSurfaceOverride: Theme.isConnectedEffect
readonly property int launcherAnimationDuration: Theme.isConnectedEffect ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration readonly property int launcherAnimationDuration: Theme.isConnectedEffect ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
@@ -93,10 +157,11 @@ Item {
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
// Shadow padding for the content window (render padding only, no motion padding) // 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 var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6 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 shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowPad: Theme.snap(shadowRenderPadding, dpr) readonly property real shadowPad: Theme.snap(shadowRenderPadding, 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)
@@ -123,6 +188,107 @@ Item {
signal dialogClosed signal dialogClosed
// ─── Connected chrome sync ────────────────────────────────────────────────
property string _chromeClaimId: ""
property bool _fullSyncPending: false
function _nextChromeClaimId() {
return "dms:launcher-v2:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
}
function _currentScreenName() {
return effectiveScreen ? effectiveScreen.name : "";
}
function _publishModalChromeState() {
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalState(screenName, {
"visible": spotlightOpen || contentWindow.visible,
"barSide": resolvedConnectedBarSide,
"bodyX": alignedX,
"bodyY": alignedY,
"bodyW": alignedWidth,
"bodyH": alignedHeight,
"animX": contentContainer ? contentContainer.animX : 0,
"animY": contentContainer ? contentContainer.animY : 0,
"omitStartConnector": false,
"omitEndConnector": false
});
}
function _syncModalChromeState() {
if (!frameOwnsConnectedChrome) {
_releaseModalChrome();
return;
}
if (!_chromeClaimId)
_chromeClaimId = _nextChromeClaimId();
_publishModalChromeState();
if (_dockBlocksEmergence && (spotlightOpen || contentWindow.visible))
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
else
ConnectedModeState.releaseDockRetract(_chromeClaimId);
}
function _flushFullSync() {
_fullSyncPending = false;
_syncModalChromeState();
}
function _queueFullSync() {
if (_fullSyncPending) return;
_fullSyncPending = true;
Qt.callLater(() => {
if (root && typeof root._flushFullSync === "function")
root._flushFullSync();
});
}
function _syncModalAnim() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName || !contentContainer) return;
ConnectedModeState.setModalAnim(screenName, contentContainer.animX, contentContainer.animY);
}
function _syncModalBody() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight);
}
function _releaseModalChrome() {
if (_chromeClaimId) {
ConnectedModeState.releaseDockRetract(_chromeClaimId);
_chromeClaimId = "";
}
const screenName = _currentScreenName();
if (screenName)
ConnectedModeState.clearModalState(screenName);
}
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
onResolvedConnectedBarSideChanged: _queueFullSync()
onSpotlightOpenChanged: _queueFullSync()
onAlignedXChanged: _syncModalBody()
onAlignedYChanged: _syncModalBody()
onAlignedWidthChanged: _syncModalBody()
onAlignedHeightChanged: _syncModalBody()
Component.onDestruction: _releaseModalChrome()
Connections {
target: contentWindow
function onVisibleChanged() {
if (contentWindow.visible)
root._syncModalChromeState();
else
root._releaseModalChrome();
}
}
function _ensureContentLoadedAndInitialize(query, mode) { function _ensureContentLoadedAndInitialize(query, mode) {
_pendingQuery = query || ""; _pendingQuery = query || "";
_pendingMode = mode || ""; _pendingMode = mode || "";
@@ -418,12 +584,12 @@ Item {
WindowBlur { WindowBlur {
targetWindow: contentWindow targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, contentContainer.scaleValue) readonly property real s: Math.min(1, contentContainer.scaleValue)
blurX: root._ccX + root.alignedWidth * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) blurX: root._ccX + root.alignedWidth * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: root._ccY + root.alignedHeight * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) blurY: root._ccY + root.alignedHeight * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedWidth * s : 0 blurWidth: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 && !root.frameOwnsConnectedChrome ? root.alignedWidth * s : 0
blurHeight: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedHeight * s : 0 blurHeight: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 && !root.frameOwnsConnectedChrome ? root.alignedHeight * s : 0
blurRadius: root.cornerRadius blurRadius: root.cornerRadius
} }
@@ -491,7 +657,16 @@ Item {
readonly property bool directionalEffect: Theme.isDirectionalEffect readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect readonly property bool depthEffect: Theme.isDepthEffect
readonly property real _connectedTravelX: Math.max(Theme.effectAnimOffset, root.alignedWidth + Theme.spacingL)
readonly property real _connectedTravelY: Math.max(Theme.effectAnimOffset, root.alignedHeight + Theme.spacingL)
readonly property real collapsedMotionX: { readonly property real collapsedMotionX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left": return -_connectedTravelX;
case "right": return _connectedTravelX;
}
return 0;
}
if (directionalEffect) { if (directionalEffect) {
if (dockLeft) if (dockLeft)
return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset); return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset);
@@ -503,6 +678,13 @@ Item {
return 0; return 0;
} }
readonly property real collapsedMotionY: { readonly property real collapsedMotionY: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top": return -_connectedTravelY;
case "bottom": return _connectedTravelY;
}
return 0;
}
if (directionalEffect) { if (directionalEffect) {
if (dockTop) if (dockTop)
return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset); return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset);
@@ -520,6 +702,9 @@ Item {
property real animY: root._motionActive ? 0 : root._frozenMotionY property real animY: root._motionActive ? 0 : root._frozenMotionY
property real scaleValue: root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed)) property real scaleValue: root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed))
onAnimXChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
onAnimYChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
Behavior on animX { Behavior on animX {
enabled: root.animationsEnabled enabled: root.animationsEnabled
DankAnim { DankAnim {
@@ -575,11 +760,11 @@ Item {
y: contentWrapper.y y: contentWrapper.y
level: root.shadowLevel level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset fallbackOffset: root.shadowFallbackOffset
targetColor: root.backgroundColor targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.backgroundColor
borderColor: root.effectiveBorderColor borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.effectiveBorderWidth borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
targetRadius: root.cornerRadius targetRadius: root.cornerRadius
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" 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 // contentWrapper moves inside static contentContainer — DankPopout pattern

View File

@@ -281,13 +281,16 @@ FocusScope {
anchors.rightMargin: root.parentModal?.borderWidth ?? 1 anchors.rightMargin: root.parentModal?.borderWidth ?? 1
anchors.bottomMargin: root.parentModal?.borderWidth ?? 1 anchors.bottomMargin: root.parentModal?.borderWidth ?? 1
readonly property bool showFooter: SettingsData.dankLauncherV2Size !== "micro" && SettingsData.dankLauncherV2ShowFooter readonly property bool showFooter: SettingsData.dankLauncherV2Size !== "micro" && SettingsData.dankLauncherV2ShowFooter
height: showFooter ? 36 : 0 readonly property bool _connectedArcAtFooter: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "bottom")
height: showFooter ? (_connectedArcAtFooter ? 76 : 36) : 0
visible: showFooter visible: showFooter
clip: true clip: true
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: -Theme.cornerRadius anchors.topMargin: -Theme.cornerRadius
// In connected mode the launcher provides the surface so update the toolbar for arcs
visible: !(root.parentModal?.frameOwnsConnectedChrome ?? false)
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius radius: Theme.cornerRadius
} }
@@ -295,7 +298,7 @@ FocusScope {
Row { Row {
id: modeButtonsRow id: modeButtonsRow
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingXS anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: 2 spacing: 2
@@ -367,7 +370,7 @@ FocusScope {
Row { Row {
id: hintsRow id: hintsRow
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: Theme.spacingM spacing: Theme.spacingM

View File

@@ -46,9 +46,11 @@ Variants {
readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right" readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right"
readonly property bool connectedBarActiveOnEdge: Theme.isConnectedEffect && !!(dock.screen || modelData) && SettingsData.getActiveBarEdgesForScreen(dock.screen || modelData).includes(connectedBarSide) readonly property bool connectedBarActiveOnEdge: Theme.isConnectedEffect && !!(dock.screen || modelData) && SettingsData.getActiveBarEdgesForScreen(dock.screen || modelData).includes(connectedBarSide)
readonly property real connectedJoinInset: { readonly property real connectedJoinInset: {
if (!Theme.isConnectedEffect) if (Theme.isConnectedEffect)
return 0; return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness;
return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness; if (SettingsData.frameEnabled)
return SettingsData.frameEdgeInsetForSide(dock.screen || modelData, dock.connectedBarSide);
return 0;
} }
readonly property real surfaceRadius: Theme.connectedSurfaceRadius readonly property real surfaceRadius: Theme.connectedSurfaceRadius
readonly property color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency) readonly property color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
@@ -379,7 +381,16 @@ Variants {
onTriggered: dock.revealSticky = false onTriggered: dock.revealSticky = false
} }
// Flip `reveal` false when a modal claims this edge; reuses the slide animation
readonly property bool _modalRetractActive: {
if (!dock._dockScreenName) return false;
return ConnectedModeState.dockRetractActiveForSide(dock._dockScreenName, dock.connectedBarSide);
}
property bool reveal: { property bool reveal: {
if (_modalRetractActive)
return false;
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) { if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
return true; return true;
} }

View File

@@ -46,6 +46,7 @@ PanelWindow {
"y": 0 "y": 0
}) })
readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState
readonly property var _modalState: ConnectedModeState.modalStates[win._screenName] || ConnectedModeState.emptyModalState
// ─── Connected chrome convenience properties ────────────────────────────── // ─── Connected chrome convenience properties ──────────────────────────────
readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive
@@ -94,6 +95,22 @@ PanelWindow {
readonly property real _effectiveNotifFarEndCcr: win._notifState.omitEndConnector ? win._effectiveNotifFarCcr : 0 readonly property real _effectiveNotifFarEndCcr: win._notifState.omitEndConnector ? win._effectiveNotifFarCcr : 0
readonly property real _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr) readonly property real _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr)
readonly property real _effectiveNotifFarExtent: Math.max(win._effectiveNotifFarStartCcr, win._effectiveNotifFarEndCcr) readonly property real _effectiveNotifFarExtent: Math.max(win._effectiveNotifFarStartCcr, win._effectiveNotifFarEndCcr)
readonly property real _effectiveModalCcr: {
const isHoriz = win._modalState.barSide === "top" || win._modalState.barSide === "bottom";
const crossSize = isHoriz ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height;
const extent = isHoriz ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width;
return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, extent, crossSize / 2)), win._dpr);
}
readonly property real _effectiveModalFarCcr: {
const isHoriz = win._modalState.barSide === "top" || win._modalState.barSide === "bottom";
const crossSize = isHoriz ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height;
return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr);
}
readonly property real _effectiveModalStartCcr: win._modalState.omitStartConnector ? 0 : win._effectiveModalCcr
readonly property real _effectiveModalEndCcr: win._modalState.omitEndConnector ? 0 : win._effectiveModalCcr
readonly property real _effectiveModalFarStartCcr: win._modalState.omitStartConnector ? win._effectiveModalFarCcr : 0
readonly property real _effectiveModalFarEndCcr: win._modalState.omitEndConnector ? win._effectiveModalFarCcr : 0
readonly property real _effectiveModalFarExtent: Math.max(win._effectiveModalFarStartCcr, win._effectiveModalFarEndCcr)
readonly property color _surfaceColor: Theme.connectedSurfaceColor readonly property color _surfaceColor: Theme.connectedSurfaceColor
readonly property real _surfaceOpacity: _surfaceColor.a readonly property real _surfaceOpacity: _surfaceColor.a
readonly property color _opaqueSurfaceColor: Qt.rgba(_surfaceColor.r, _surfaceColor.g, _surfaceColor.b, 1) readonly property color _opaqueSurfaceColor: Qt.rgba(_surfaceColor.r, _surfaceColor.g, _surfaceColor.b, 1)
@@ -403,6 +420,180 @@ PanelWindow {
height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0 height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0
} }
Item {
id: _modalBodyBlurAnchor
visible: false
readonly property bool _active: win._frameActive && win._modalState.visible && win._modalState.bodyW > 0 && win._modalState.bodyH > 0
// Clamp animX/Y so the blur body shrinks toward the bar edge (same as _popoutBodyBlurAnchor).
readonly property real _dyClamp: (win._modalState.barSide === "top" || win._modalState.barSide === "bottom") ? Math.max(-win._modalState.bodyH, Math.min(win._modalState.animY, win._modalState.bodyH)) : 0
readonly property real _dxClamp: (win._modalState.barSide === "left" || win._modalState.barSide === "right") ? Math.max(-win._modalState.bodyW, Math.min(win._modalState.animX, win._modalState.bodyW)) : 0
x: _active ? Theme.snap(win._modalState.bodyX + (win._modalState.barSide === "right" ? _dxClamp : 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalState.bodyY + (win._modalState.barSide === "bottom" ? _dyClamp : 0), win._dpr) : 0
width: _active ? Theme.snap(Math.max(0, win._modalState.bodyW - Math.abs(_dxClamp)), win._dpr) : 0
height: _active ? Theme.snap(Math.max(0, win._modalState.bodyH - Math.abs(_dyClamp)), win._dpr) : 0
}
Item {
id: _modalBodyBlurCap
opacity: 0
readonly property string _side: win._modalState.barSide
readonly property real _capThickness: win._modalBlurCapThickness()
readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0
readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capThickness, _modalBodyBlurAnchor.width) : _modalBodyBlurAnchor.width
readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capThickness, _modalBodyBlurAnchor.height) : _modalBodyBlurAnchor.height
x: !_active ? 0 : (_side === "right" ? _modalBodyBlurAnchor.x + _modalBodyBlurAnchor.width - _capWidth : _modalBodyBlurAnchor.x)
y: !_active ? 0 : (_side === "bottom" ? _modalBodyBlurAnchor.y + _modalBodyBlurAnchor.height - _capHeight : _modalBodyBlurAnchor.y)
width: _active ? _capWidth : 0
height: _active ? _capHeight : 0
}
Item {
id: _modalLeftConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._modalConnectorRadius("left")
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
readonly property real _w: win._modalConnectorWidth(0, "left")
readonly property real _h: win._modalConnectorHeight(0, "left")
x: _active ? Theme.snap(win._modalConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "left", 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalConnectorY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "left", 0), win._dpr) : 0
width: _active ? _w : 0
height: _active ? _h : 0
}
Item {
id: _modalRightConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._modalConnectorRadius("right")
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
readonly property real _w: win._modalConnectorWidth(0, "right")
readonly property real _h: win._modalConnectorHeight(0, "right")
x: _active ? Theme.snap(win._modalConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "right", 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalConnectorY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "right", 0), win._dpr) : 0
width: _active ? _w : 0
height: _active ? _h : 0
}
Item {
id: _modalLeftConnectorCutout
opacity: 0
readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0
readonly property string _arcCorner: win._connectorArcCorner(win._modalState.barSide, "left")
readonly property real _radius: win._modalConnectorRadius("left")
x: _active ? win._connectorCutoutX(_modalLeftConnectorBlurAnchor.x, _modalLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalLeftConnectorBlurAnchor.y, _modalLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalRightConnectorCutout
opacity: 0
readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0
readonly property string _arcCorner: win._connectorArcCorner(win._modalState.barSide, "right")
readonly property real _radius: win._modalConnectorRadius("right")
x: _active ? win._connectorCutoutX(_modalRightConnectorBlurAnchor.x, _modalRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalRightConnectorBlurAnchor.y, _modalRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalFarStartConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._effectiveModalFarStartCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarStartBodyBlurCap
opacity: 0
readonly property real _radius: win._effectiveModalFarStartCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "left", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarEndBodyBlurCap
opacity: 0
readonly property real _radius: win._effectiveModalFarEndCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "right", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarEndConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._effectiveModalFarEndCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarStartConnectorCutout
opacity: 0
readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "left")
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "left")
readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement)
readonly property real _radius: win._effectiveModalFarStartCcr
x: _active ? win._connectorCutoutX(_modalFarStartConnectorBlurAnchor.x, _modalFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalFarStartConnectorBlurAnchor.y, _modalFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalFarEndConnectorCutout
opacity: 0
readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "right")
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "right")
readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement)
readonly property real _radius: win._effectiveModalFarEndCcr
x: _active ? win._connectorCutoutX(_modalFarEndConnectorBlurAnchor.x, _modalFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalFarEndConnectorBlurAnchor.y, _modalFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item { Item {
id: _notifBodySceneBlurAnchor id: _notifBodySceneBlurAnchor
visible: false visible: false
@@ -704,6 +895,53 @@ PanelWindow {
radius: win._effectiveNotifFarEndCcr radius: win._effectiveNotifFarEndCcr
} }
} }
// ── Connected modal blur regions ──
Region {
item: _modalBodyBlurAnchor
radius: win._surfaceRadius
}
Region {
item: _modalBodyBlurCap
}
Region {
item: _modalLeftConnectorBlurAnchor
Region {
item: _modalLeftConnectorCutout
intersection: Intersection.Subtract
radius: win._modalConnectorRadius("left")
}
}
Region {
item: _modalRightConnectorBlurAnchor
Region {
item: _modalRightConnectorCutout
intersection: Intersection.Subtract
radius: win._modalConnectorRadius("right")
}
}
Region {
item: _modalFarStartBodyBlurCap
}
Region {
item: _modalFarEndBodyBlurCap
}
Region {
item: _modalFarStartConnectorBlurAnchor
Region {
item: _modalFarStartConnectorCutout
intersection: Intersection.Subtract
radius: win._effectiveModalFarStartCcr
}
}
Region {
item: _modalFarEndConnectorBlurAnchor
Region {
item: _modalFarEndConnectorCutout
intersection: Intersection.Subtract
radius: win._effectiveModalFarEndCcr
}
}
} }
// ─── Connector position helpers ──────────────────────────────────────── // ─── Connector position helpers ────────────────────────────────────────
@@ -894,6 +1132,52 @@ PanelWindow {
return (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0; return (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0;
} }
function _modalArcExtent() {
return (win._modalState.barSide === "top" || win._modalState.barSide === "bottom") ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width;
}
function _modalBlurCapThickness() {
const extent = win._modalArcExtent();
return Math.max(0, Math.min(win._effectiveModalCcr, extent - win._surfaceRadius));
}
function _modalConnectorRadius(placement) {
return placement === "right" ? win._effectiveModalEndCcr : win._effectiveModalStartCcr;
}
function _modalConnectorWidth(spacing, placement) {
const isVert = win._modalState.barSide === "left" || win._modalState.barSide === "right";
const radius = win._modalConnectorRadius(placement);
return isVert ? (spacing + radius) : radius;
}
function _modalConnectorHeight(spacing, placement) {
const isVert = win._modalState.barSide === "left" || win._modalState.barSide === "right";
const radius = win._modalConnectorRadius(placement);
return isVert ? radius : (spacing + radius);
}
function _modalConnectorX(baseX, bodyWidth, placement, spacing) {
const barSide = win._modalState.barSide;
const isVert = barSide === "left" || barSide === "right";
const seamX = !isVert ? (placement === "left" ? baseX : baseX + bodyWidth) : (barSide === "left" ? baseX : baseX + bodyWidth);
const w = _modalConnectorWidth(spacing, placement);
if (!isVert)
return placement === "left" ? seamX - w : seamX;
return barSide === "left" ? seamX : seamX - w;
}
function _modalConnectorY(baseY, bodyHeight, placement, spacing) {
const barSide = win._modalState.barSide;
const seamY = barSide === "top" ? baseY : barSide === "bottom" ? baseY + bodyHeight : (placement === "left" ? baseY : baseY + bodyHeight);
const h = _modalConnectorHeight(spacing, placement);
if (barSide === "top")
return seamY;
if (barSide === "bottom")
return seamY - h;
return placement === "left" ? seamY - h : seamY;
}
function _popoutArcExtent() { function _popoutArcExtent() {
return (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? _popoutBodyBlurAnchor.height : _popoutBodyBlurAnchor.width; return (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? _popoutBodyBlurAnchor.height : _popoutBodyBlurAnchor.width;
} }
@@ -1343,5 +1627,61 @@ PanelWindow {
y: 0 y: 0
} }
} }
// Bar-side-bounded clip so modal chrome retracts behind the bar on exit
// instead of sliding over bar widgets (mirrors the popout `_popoutClip`).
Item {
id: _modalClip
visible: _modalBodyBlurAnchor._active
z: 1
readonly property string _modalSide: win._modalState.barSide
readonly property real _inset: _modalBodyBlurAnchor._active && win.screen ? SettingsData.frameEdgeInsetForSide(win.screen, _modalSide) : 0
readonly property real _topBound: _modalSide === "top" ? _inset : 0
readonly property real _bottomBound: _modalSide === "bottom" ? (win.height - _inset) : win.height
readonly property real _leftBound: _modalSide === "left" ? _inset : 0
readonly property real _rightBound: _modalSide === "right" ? (win.width - _inset) : win.width
x: _leftBound
y: _topBound
width: Math.max(0, _rightBound - _leftBound)
height: Math.max(0, _bottomBound - _topBound)
clip: true
Item {
id: _modalChrome
readonly property string _modalSide: win._modalState.barSide
readonly property bool _isHoriz: _modalSide === "top" || _modalSide === "bottom"
readonly property real _startCcr: win._effectiveModalStartCcr
readonly property real _endCcr: win._effectiveModalEndCcr
readonly property real _farExtent: win._effectiveModalFarExtent
readonly property real _bodyOffsetX: _isHoriz ? _startCcr : (_modalSide === "right" ? _farExtent : 0)
readonly property real _bodyOffsetY: _isHoriz ? (_modalSide === "bottom" ? _farExtent : 0) : _startCcr
readonly property real _bodyW: Theme.snap(_modalBodyBlurAnchor.width, win._dpr)
readonly property real _bodyH: Theme.snap(_modalBodyBlurAnchor.height, win._dpr)
x: Theme.snap(_modalBodyBlurAnchor.x - _bodyOffsetX - _modalClip.x, win._dpr)
y: Theme.snap(_modalBodyBlurAnchor.y - _bodyOffsetY - _modalClip.y, win._dpr)
width: _isHoriz ? Theme.snap(_bodyW + _startCcr + _endCcr, win._dpr) : Theme.snap(_bodyW + _farExtent, win._dpr)
height: _isHoriz ? Theme.snap(_bodyH + _farExtent, win._dpr) : Theme.snap(_bodyH + _startCcr + _endCcr, win._dpr)
ConnectedShape {
visible: _modalBodyBlurAnchor._active && _modalChrome._bodyW > 0 && _modalChrome._bodyH > 0
barSide: _modalChrome._modalSide
bodyWidth: _modalChrome._bodyW
bodyHeight: _modalChrome._bodyH
connectorRadius: win._effectiveModalCcr
startConnectorRadius: _modalChrome._startCcr
endConnectorRadius: _modalChrome._endCcr
farStartConnectorRadius: win._effectiveModalFarStartCcr
farEndConnectorRadius: win._effectiveModalFarEndCcr
surfaceRadius: win._surfaceRadius
fillColor: win._opaqueSurfaceColor
x: 0
y: 0
}
}
}
} }
} }

View File

@@ -360,11 +360,18 @@ PanelWindow {
function _frameEdgeInset(side) { function _frameEdgeInset(side) {
if (!screen) if (!screen)
return 0; return 0;
const edges = SettingsData.getActiveBarEdgesForScreen(screen); const raw = SettingsData.frameEdgeInsetForSide(screen, side);
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
return Math.max(0, Math.round(Theme.px(raw, dpr))); return Math.max(0, Math.round(Theme.px(raw, dpr)));
} }
readonly property bool frameOnlyNoConnected: SettingsData.frameEnabled && !connectedFrameMode && !!screen
&& SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences)
// Frame ON + Connected OFF. frameEdgeInset is the full bar/frame inset
function _frameGapMargin(side) {
return _frameEdgeInset(side) + Theme.popupDistance;
}
function getTopMargin() { function getTopMargin() {
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left; const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
@@ -377,6 +384,8 @@ PanelWindow {
: (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("top") + cornerClear + screenY; return _frameEdgeInset("top") + cornerClear + screenY;
} }
if (frameOnlyNoConnected)
return _frameGapMargin("top") + screenY;
const barInfo = getBarInfo(); const barInfo = getBarInfo();
const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance; const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
return base + screenY; return base + screenY;
@@ -394,6 +403,8 @@ PanelWindow {
: (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("bottom") + cornerClear + screenY; return _frameEdgeInset("bottom") + cornerClear + screenY;
} }
if (frameOnlyNoConnected)
return _frameGapMargin("bottom") + screenY;
const barInfo = getBarInfo(); const barInfo = getBarInfo();
const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance; const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
return base + screenY; return base + screenY;
@@ -410,6 +421,8 @@ PanelWindow {
if (connectedFrameMode) if (connectedFrameMode)
return _frameEdgeInset("left"); return _frameEdgeInset("left");
if (frameOnlyNoConnected)
return _frameGapMargin("left");
const barInfo = getBarInfo(); const barInfo = getBarInfo();
return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance; return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
} }
@@ -425,6 +438,8 @@ PanelWindow {
if (connectedFrameMode) if (connectedFrameMode)
return _frameEdgeInset("right"); return _frameEdgeInset("right");
if (frameOnlyNoConnected)
return _frameGapMargin("right");
const barInfo = getBarInfo(); const barInfo = getBarInfo();
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance; return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
} }

View File

@@ -314,6 +314,22 @@ Item {
opacity: enabled ? 1.0 : 0.5 opacity: enabled ? 1.0 : 0.5
onToggled: checked => SettingsData.set("frameCloseGaps", checked) onToggled: checked => SettingsData.set("frameCloseGaps", checked)
} }
SettingsButtonGroupRow {
visible: SettingsData.frameEnabled
settingKey: "frameLauncherEmergeSide"
tags: ["frame", "connected", "launcher", "modal", "emerge", "direction", "bottom", "top"]
text: I18n.tr("Launcher Emerge Side")
description: I18n.tr("Which frame edge the Launcher slides in from. Other modals emerge from the opposite side.")
model: [I18n.tr("Bottom"), I18n.tr("Top")]
currentIndex: SettingsData.frameLauncherEmergeSide === "top" ? 1 : 0
enabled: SettingsData.connectedFrameModeActive
opacity: enabled ? 1.0 : 0.5
onSelectionChanged: (index, selected) => {
if (!selected) return;
SettingsData.set("frameLauncherEmergeSide", index === 1 ? "top" : "bottom");
}
}
} }
// ── Display Assignment ──────────────────────────────────────────── // ── Display Assignment ────────────────────────────────────────────