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:
@@ -278,4 +278,193 @@ Singleton {
|
||||
notificationStates = next;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +237,9 @@ Singleton {
|
||||
onFrameBlurEnabledChanged: saveSettings()
|
||||
property bool frameCloseGaps: false
|
||||
onFrameCloseGapsChanged: saveSettings()
|
||||
property string frameLauncherEmergeSide: "bottom"
|
||||
onFrameLauncherEmergeSideChanged: saveSettings()
|
||||
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
|
||||
property int previousDirectionalMode: 1
|
||||
onPreviousDirectionalModeChanged: saveSettings()
|
||||
property var connectedFrameBarStyleBackups: ({})
|
||||
@@ -2206,6 +2209,12 @@ Singleton {
|
||||
return edges;
|
||||
}
|
||||
|
||||
function frameEdgeInsetForSide(screen, side) {
|
||||
if (!frameEnabled || !screen) return 0;
|
||||
const edges = getActiveBarEdgesForScreen(screen);
|
||||
return edges.includes(side) ? frameBarSize : frameThickness;
|
||||
}
|
||||
|
||||
function getActiveBarThicknessForScreen(screen) {
|
||||
if (frameEnabled) return frameBarSize;
|
||||
if (!screen) return frameThickness;
|
||||
|
||||
@@ -553,7 +553,8 @@ var SPEC = {
|
||||
frameBarSize: { def: 40 },
|
||||
frameShowOnOverview: { def: false },
|
||||
frameBlurEnabled: { def: true },
|
||||
frameCloseGaps: { def: false }
|
||||
frameCloseGaps: { def: false },
|
||||
frameLauncherEmergeSide: { def: "bottom" }
|
||||
};
|
||||
|
||||
function getValidKeys() {
|
||||
|
||||
@@ -26,6 +26,33 @@ Item {
|
||||
property bool closeOnEscapeKey: true
|
||||
property bool closeOnBackgroundClick: true
|
||||
property string animationType: "scale"
|
||||
|
||||
// Opposite side from the launcher by default; subclasses may override
|
||||
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
||||
|
||||
readonly property bool frameConnectedMode: SettingsData.frameEnabled
|
||||
&& Theme.isConnectedEffect
|
||||
&& !!effectiveScreen
|
||||
&& SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
||||
|
||||
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
|
||||
property int animationDuration: connectedMotionParity ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
|
||||
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||
@@ -66,6 +93,107 @@ Item {
|
||||
|
||||
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() {
|
||||
closeTimer.stop();
|
||||
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 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: {
|
||||
if (Theme.isConnectedEffect)
|
||||
return 0;
|
||||
@@ -187,7 +317,47 @@ Item {
|
||||
readonly property real alignedWidth: Theme.px(modalWidth, 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) {
|
||||
case "center":
|
||||
return (screenWidth - alignedWidth) / 2;
|
||||
@@ -200,7 +370,7 @@ Item {
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real alignedY: Theme.snap((() => {
|
||||
readonly property real alignedY: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedY : (() => {
|
||||
switch (positioning) {
|
||||
case "center":
|
||||
return (screenHeight - alignedHeight) / 2;
|
||||
@@ -271,12 +441,12 @@ Item {
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: contentWindow
|
||||
blurEnabled: root.effectiveBlurEnabled
|
||||
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
||||
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
||||
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
||||
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||
blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0
|
||||
blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0
|
||||
blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
||||
blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
||||
blurRadius: root.effectiveCornerRadius
|
||||
}
|
||||
|
||||
@@ -392,7 +562,17 @@ Item {
|
||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||
readonly property real customDistTop: customAnchorY
|
||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
|
||||
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||
readonly property real offsetX: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "left": return -connectedEmergenceTravelX;
|
||||
case "right": return connectedEmergenceTravelX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
|
||||
return 0;
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
@@ -428,6 +608,13 @@ Item {
|
||||
return 0;
|
||||
}
|
||||
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)
|
||||
return 0;
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
@@ -467,6 +654,9 @@ Item {
|
||||
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
|
||||
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
|
||||
property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed
|
||||
|
||||
@@ -527,18 +717,18 @@ Item {
|
||||
level: root.shadowLevel
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: root.effectiveCornerRadius
|
||||
targetColor: root.effectiveBackgroundColor
|
||||
borderColor: root.effectiveBorderColor
|
||||
borderWidth: root.effectiveBorderWidth
|
||||
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.effectiveCornerRadius
|
||||
color: "transparent"
|
||||
border.color: root.connectedSurfaceOverride ? "transparent" : BlurService.borderColor
|
||||
border.width: root.connectedSurfaceOverride ? 0 : BlurService.borderWidth
|
||||
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
||||
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
||||
z: 100
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,72 @@ Item {
|
||||
}
|
||||
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 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 int launcherAnimationDuration: Theme.isConnectedEffect ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
|
||||
@@ -93,10 +157,11 @@ Item {
|
||||
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||
|
||||
// Shadow padding for the content window (render padding only, no motion padding)
|
||||
// Shadow padding for the content window (render padding only, no motion padding).
|
||||
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (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 alignedWidth: Theme.px(modalWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||
@@ -123,6 +188,107 @@ Item {
|
||||
|
||||
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) {
|
||||
_pendingQuery = query || "";
|
||||
_pendingMode = mode || "";
|
||||
@@ -418,12 +584,12 @@ Item {
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: contentWindow
|
||||
blurEnabled: root.effectiveBlurEnabled
|
||||
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
||||
readonly property real s: Math.min(1, contentContainer.scaleValue)
|
||||
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)
|
||||
blurWidth: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedWidth * s : 0
|
||||
blurHeight: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedHeight * 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.frameOwnsConnectedChrome ? root.alignedHeight * s : 0
|
||||
blurRadius: root.cornerRadius
|
||||
}
|
||||
|
||||
@@ -491,7 +657,16 @@ Item {
|
||||
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
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: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "left": return -_connectedTravelX;
|
||||
case "right": return _connectedTravelX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (directionalEffect) {
|
||||
if (dockLeft)
|
||||
return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset);
|
||||
@@ -503,6 +678,13 @@ Item {
|
||||
return 0;
|
||||
}
|
||||
readonly property real collapsedMotionY: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "top": return -_connectedTravelY;
|
||||
case "bottom": return _connectedTravelY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (directionalEffect) {
|
||||
if (dockTop)
|
||||
return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset);
|
||||
@@ -520,6 +702,9 @@ Item {
|
||||
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))
|
||||
|
||||
onAnimXChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
|
||||
onAnimYChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
|
||||
|
||||
Behavior on animX {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
@@ -575,11 +760,11 @@ Item {
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetColor: root.backgroundColor
|
||||
borderColor: root.effectiveBorderColor
|
||||
borderWidth: root.effectiveBorderWidth
|
||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.backgroundColor
|
||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||
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
|
||||
|
||||
@@ -281,13 +281,16 @@ FocusScope {
|
||||
anchors.rightMargin: root.parentModal?.borderWidth ?? 1
|
||||
anchors.bottomMargin: root.parentModal?.borderWidth ?? 1
|
||||
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
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
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)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
@@ -295,7 +298,7 @@ FocusScope {
|
||||
Row {
|
||||
id: modeButtonsRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingXS
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
|
||||
spacing: 2
|
||||
@@ -367,7 +370,7 @@ FocusScope {
|
||||
Row {
|
||||
id: hintsRow
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
|
||||
spacing: Theme.spacingM
|
||||
|
||||
@@ -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 bool connectedBarActiveOnEdge: Theme.isConnectedEffect && !!(dock.screen || modelData) && SettingsData.getActiveBarEdgesForScreen(dock.screen || modelData).includes(connectedBarSide)
|
||||
readonly property real connectedJoinInset: {
|
||||
if (!Theme.isConnectedEffect)
|
||||
return 0;
|
||||
if (Theme.isConnectedEffect)
|
||||
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 color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
||||
@@ -379,7 +381,16 @@ Variants {
|
||||
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: {
|
||||
if (_modalRetractActive)
|
||||
return false;
|
||||
|
||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ PanelWindow {
|
||||
"y": 0
|
||||
})
|
||||
readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState
|
||||
readonly property var _modalState: ConnectedModeState.modalStates[win._screenName] || ConnectedModeState.emptyModalState
|
||||
|
||||
// ─── Connected chrome convenience properties ──────────────────────────────
|
||||
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 _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr)
|
||||
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 real _surfaceOpacity: _surfaceColor.a
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
id: _notifBodySceneBlurAnchor
|
||||
visible: false
|
||||
@@ -704,6 +895,53 @@ PanelWindow {
|
||||
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 ────────────────────────────────────────
|
||||
@@ -894,6 +1132,52 @@ PanelWindow {
|
||||
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() {
|
||||
return (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? _popoutBodyBlurAnchor.height : _popoutBodyBlurAnchor.width;
|
||||
}
|
||||
@@ -1343,5 +1627,61 @@ PanelWindow {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,11 +360,18 @@ PanelWindow {
|
||||
function _frameEdgeInset(side) {
|
||||
if (!screen)
|
||||
return 0;
|
||||
const edges = SettingsData.getActiveBarEdgesForScreen(screen);
|
||||
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
const raw = SettingsData.frameEdgeInsetForSide(screen, side);
|
||||
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() {
|
||||
const popupPos = SettingsData.notificationPopupPosition;
|
||||
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));
|
||||
return _frameEdgeInset("top") + cornerClear + screenY;
|
||||
}
|
||||
if (frameOnlyNoConnected)
|
||||
return _frameGapMargin("top") + screenY;
|
||||
const barInfo = getBarInfo();
|
||||
const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
|
||||
return base + screenY;
|
||||
@@ -394,6 +403,8 @@ PanelWindow {
|
||||
: (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
|
||||
return _frameEdgeInset("bottom") + cornerClear + screenY;
|
||||
}
|
||||
if (frameOnlyNoConnected)
|
||||
return _frameGapMargin("bottom") + screenY;
|
||||
const barInfo = getBarInfo();
|
||||
const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
|
||||
return base + screenY;
|
||||
@@ -410,6 +421,8 @@ PanelWindow {
|
||||
|
||||
if (connectedFrameMode)
|
||||
return _frameEdgeInset("left");
|
||||
if (frameOnlyNoConnected)
|
||||
return _frameGapMargin("left");
|
||||
const barInfo = getBarInfo();
|
||||
return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
|
||||
}
|
||||
@@ -425,6 +438,8 @@ PanelWindow {
|
||||
|
||||
if (connectedFrameMode)
|
||||
return _frameEdgeInset("right");
|
||||
if (frameOnlyNoConnected)
|
||||
return _frameGapMargin("right");
|
||||
const barInfo = getBarInfo();
|
||||
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
|
||||
}
|
||||
|
||||
@@ -314,6 +314,22 @@ Item {
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
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 ────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user