1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-16 16:15:23 -04:00

Compare commits

...

5 Commits

Author SHA1 Message Date
jbwfu 820fa07846 feat(settings): add clipboard entry action visibility controls (#2621)
* feat(settings): add clipboard entry action visibility controls

* fix(clipboard): show pinned indicator for saved entries when pin action is hidden
2026-06-12 14:08:05 -04:00
purian23 66794582c9 fix(fullscreen): retain user dbar standalone configs while in framemode fullscreen 2026-06-12 12:39:38 -04:00
jbwfu 73eb471ae3 fix(clipboard): keep first item selected when navigating upward (#2622) 2026-06-12 11:35:07 -04:00
jbwfu 0f2f4b96c4 Fix/clipboard confirmation keyboard safety (#2623)
* fix(clipboard): improve confirmation dialog keyboard focus

* fix(clipboard): require confirmation for clear-all shortcut
2026-06-12 11:34:16 -04:00
purian23 d53809cf2b refactor(framemode): unify connected surface chrome via SDF pipeline
- Shadow system rewrite with SDF quads
- Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders
- Improve frame blur performance
- Plugin performance gate
2026-06-12 11:03:39 -04:00
61 changed files with 2753 additions and 2844 deletions
@@ -235,7 +235,7 @@ Conditionally show/hide the bar pill:
```qml ```qml
PluginComponent { PluginComponent {
visibilityCommand: "pgrep -x myapp" visibilityCommand: "pgrep -x myapp"
visibilityInterval: 5000 // check every 5 seconds visibilityInterval: 5 // seconds between checks; polling pauses while the bar is hidden
} }
``` ```
+57 -71
View File
@@ -7,6 +7,7 @@ Item {
required property var modalHandle required property var modalHandle
required property string claimPrefix required property string claimPrefix
property string surfaceKind: "modal"
property string screenName: "" property string screenName: ""
property bool enabled: false property bool enabled: false
property bool active: false property bool active: false
@@ -14,112 +15,97 @@ Item {
property bool dockBlocked: false property bool dockBlocked: false
property string dockSide: "" property string dockSide: ""
property string claimId: "" property alias claimId: lease.claimId
property string claimedScreenName: "" property alias claimedScreenName: lease.claimedScreenName
signal recoveryRequested signal recoveryRequested
visible: false visible: false
function _nextClaimId() {
return claimPrefix + ":" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
}
function _isCurrentModal(name) { function _isCurrentModal(name) {
return !!name && ModalManager.isCurrentModal(modalHandle, name); return !!name && ModalManager.isCurrentModal(modalHandle, name);
} }
function _shouldRecover() { ConnectedSurfaceLease {
return active && enabled && _isCurrentModal(screenName); id: lease
claimPrefix: root.claimPrefix
screenName: root.screenName
enabled: root.enabled
active: root.active
presented: root.presented
dockBlocked: root.dockBlocked
dockSide: root.dockSide
isCurrentOwner: function(name) {
return root._isCurrentModal(name);
} }
hasOwner: function(name, ownerId) {
function _requestRecovery() { return ConnectedModeState.hasModalOwner(name, ownerId);
if (_shouldRecover()) }
recoveryRequested(); statePresent: function(name, ownerId) {
return ConnectedModeState.hasModalOwner(name, ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, root.surfaceKind, ownerId);
}
claimState: function(name, state, ownerId) {
return ConnectedModeState.claimModalState(name, state, ownerId);
}
ensureState: function(name, state, ownerId) {
return ConnectedModeState.ensureModalState(name, state, ownerId);
}
releaseState: function(name, ownerId) {
return ConnectedModeState.clearModalState(name, ownerId);
}
updateAnimationState: function(name, ownerId, animX, animY) {
return ConnectedModeState.setModalAnim(name, animX, animY, ownerId);
}
updateBodyState: function(name, ownerId, bodyX, bodyY, bodyW, bodyH) {
return ConnectedModeState.setModalBody(name, bodyX, bodyY, bodyW, bodyH, ownerId);
}
requestDockRetract: function(ownerId, name, side) {
return ConnectedModeState.requestDockRetract(ownerId, name, side);
}
releaseDockRetract: function(ownerId) {
return ConnectedModeState.releaseDockRetract(ownerId);
}
onRecoveryRequested: root.recoveryRequested()
} }
function publish(state) { function publish(state) {
if (!enabled || !screenName || !state) { return lease.publish(Object.assign({}, state, {
release(); "kind": root.surfaceKind,
return false; "screenName": root.screenName,
} "presented": root.presented,
if (claimedScreenName && claimedScreenName !== screenName) "dockRetractSide": root.dockBlocked ? root.dockSide : ""
release(); }), false);
const isCurrent = _isCurrentModal(screenName);
let isClaim = !claimId;
if (isClaim && !isCurrent)
return false;
if (isClaim)
claimId = _nextClaimId();
let published = isClaim ? ConnectedModeState.claimModalState(screenName, state, claimId) : ConnectedModeState.ensureModalState(screenName, state, claimId);
if (!published && !isClaim && isCurrent) {
ConnectedModeState.releaseDockRetract(claimId);
claimId = _nextClaimId();
published = ConnectedModeState.claimModalState(screenName, state, claimId);
}
if (!published)
return false;
claimedScreenName = screenName;
if (dockBlocked && presented)
ConnectedModeState.requestDockRetract(claimId, screenName, dockSide);
else
ConnectedModeState.releaseDockRetract(claimId);
return true;
} }
function updateAnim(animX, animY) { function updateAnim(animX, animY) {
if (!enabled || !claimId || !claimedScreenName) return lease.updateAnim(animX, animY);
return false;
if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) {
_requestRecovery();
return false;
}
return ConnectedModeState.setModalAnim(claimedScreenName, animX, animY, claimId);
} }
function updateBody(bodyX, bodyY, bodyW, bodyH) { function updateBody(bodyX, bodyY, bodyW, bodyH) {
if (!enabled || !claimId || !claimedScreenName) return lease.updateBody(bodyX, bodyY, bodyW, bodyH);
return false;
if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) {
_requestRecovery();
return false;
}
return ConnectedModeState.setModalBody(claimedScreenName, bodyX, bodyY, bodyW, bodyH, claimId);
} }
function release() { function release() {
if (!claimId) return lease.release();
return;
ConnectedModeState.releaseDockRetract(claimId);
const releasedClaimId = claimId;
const releasedScreenName = claimedScreenName;
claimId = "";
claimedScreenName = "";
if (releasedScreenName)
ConnectedModeState.clearModalState(releasedScreenName, releasedClaimId);
} }
Component.onDestruction: release()
Connections { Connections {
target: ModalManager target: ModalManager
function onModalChanged() { function onModalChanged() {
root._requestRecovery(); lease.requestRecovery();
} }
} }
Connections { Connections {
target: ConnectedModeState target: ConnectedModeState
function onModalOwnersChanged() { function onModalOwnersChanged() {
if (!ConnectedModeState.hasModalOwner(root.screenName, root.claimId)) lease.checkOwnershipRecovery();
root._requestRecovery();
} }
function onModalStatesChanged() { function onModalStatesChanged() {
if (!ConnectedModeState.modalStates[root.screenName]) lease.checkStateRecovery();
root._requestRecovery(); }
function onSurfaceDescriptorsChanged() {
lease.checkStateRecovery();
} }
} }
} }
+170 -23
View File
@@ -3,10 +3,123 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import "ConnectedSurfaceDescriptor.js" as SurfaceDescriptor
Singleton { Singleton {
id: root id: root
property var surfaceDescriptors: ({})
function _surfaceSlot(kind) {
return SurfaceDescriptor.slotForKind(kind);
}
function surfaceDescriptor(screenName, kind) {
const slot = _surfaceSlot(kind);
const screenDescriptors = screenName ? surfaceDescriptors[screenName] : null;
const descriptor = screenDescriptors && screenDescriptors[slot] ? screenDescriptors[slot] : SurfaceDescriptor.empty(kind, screenName);
let bodyRect = descriptor.bodyRect;
let animationOffset = descriptor.animationOffset;
if (slot === "popout" && popoutScreen === screenName) {
bodyRect = {
"x": popoutBodyX,
"y": popoutBodyY,
"width": popoutBodyW,
"height": popoutBodyH
};
animationOffset = {
"x": popoutAnimX,
"y": popoutAnimY
};
} else if (slot === "modal" && modalStates[screenName]) {
const modal = modalStates[screenName];
bodyRect = {
"x": modal.bodyX,
"y": modal.bodyY,
"width": modal.bodyW,
"height": modal.bodyH
};
animationOffset = {
"x": modal.animX,
"y": modal.animY
};
} else if (slot === "dock" && dockStates[screenName]) {
const dock = dockStates[screenName];
const slide = dockSlides[screenName] || {
"x": dock.slideX,
"y": dock.slideY
};
bodyRect = {
"x": dock.bodyX,
"y": dock.bodyY,
"width": dock.bodyW,
"height": dock.bodyH
};
animationOffset = {
"x": slide.x,
"y": slide.y
};
} else if (slot === "notification" && notificationStates[screenName]) {
const notification = notificationStates[screenName];
bodyRect = {
"x": notification.bodyX,
"y": notification.bodyY,
"width": notification.bodyW,
"height": notification.bodyH
};
}
return SurfaceDescriptor.normalize({
"bodyRect": bodyRect,
"animationOffset": animationOffset
}, descriptor);
}
function hasSurfaceDescriptor(screenName, kind, ownerId) {
const descriptor = surfaceDescriptor(screenName, kind);
return descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId);
}
function _setSurfaceDescriptor(screenName, slotKind, state, ownerId) {
if (!screenName || !state)
return false;
const slot = _surfaceSlot(slotKind);
const currentScreen = surfaceDescriptors[screenName] || {};
const previous = currentScreen[slot] || SurfaceDescriptor.empty(state.kind || slotKind, screenName);
let normalized = SurfaceDescriptor.normalize(Object.assign({}, state, {
"ownerId": ownerId !== undefined ? ownerId : previous.ownerId,
"screenName": screenName,
"revision": previous.revision
}), previous);
if (SurfaceDescriptor.same(previous, normalized))
return true;
normalized = SurfaceDescriptor.withRevision(normalized, previous.revision + 1);
const nextScreen = _cloneDict(currentScreen);
nextScreen[slot] = normalized;
const next = _cloneDict(surfaceDescriptors);
next[screenName] = nextScreen;
surfaceDescriptors = next;
return true;
}
function _clearSurfaceDescriptor(screenName, kind, ownerId) {
if (!screenName)
return false;
const slot = _surfaceSlot(kind);
const currentScreen = surfaceDescriptors[screenName];
const current = currentScreen ? currentScreen[slot] : null;
if (!current || (ownerId && current.ownerId !== ownerId))
return false;
const nextScreen = _cloneDict(currentScreen);
delete nextScreen[slot];
const next = _cloneDict(surfaceDescriptors);
if (Object.keys(nextScreen).length > 0)
next[screenName] = nextScreen;
else
delete next[screenName];
surfaceDescriptors = next;
return true;
}
readonly property var emptyDockState: ({ readonly property var emptyDockState: ({
"reveal": false, "reveal": false,
"barSide": "bottom", "barSide": "bottom",
@@ -18,7 +131,6 @@ Singleton {
"slideY": 0 "slideY": 0
}) })
// Popout state (updated by DankPopout when connectedFrameModeActive)
property string popoutOwnerId: "" property string popoutOwnerId: ""
property bool popoutVisible: false property bool popoutVisible: false
property string popoutBarSide: "top" property string popoutBarSide: "top"
@@ -32,14 +144,10 @@ Singleton {
property bool popoutOmitStartConnector: false property bool popoutOmitStartConnector: false
property bool popoutOmitEndConnector: false property bool popoutOmitEndConnector: false
// Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name
property var dockStates: ({}) property var dockStates: ({})
// Dock slide offsets — hot-path updates separated from full geometry state
property var dockSlides: ({}) property var dockSlides: ({})
// Surfaces are keyed by screen.name. FrameWindow watches to refresh connected chrome
// after claim/release boundaries without tracking each animation frame
property var surfaceRevisions: ({}) property var surfaceRevisions: ({})
function _cloneDict(src) { function _cloneDict(src) {
@@ -69,8 +177,10 @@ Singleton {
popoutOwnerId = claimId; popoutOwnerId = claimId;
const ok = updatePopout(claimId, state); const ok = updatePopout(claimId, state);
if (ok) { if (ok) {
if (previousScreen && previousScreen !== popoutScreen) if (previousScreen && previousScreen !== popoutScreen) {
_clearSurfaceDescriptor(previousScreen, "popout");
_bumpSurfaceRevision(previousScreen); _bumpSurfaceRevision(previousScreen);
}
_bumpSurfaceRevision(popoutScreen); _bumpSurfaceRevision(popoutScreen);
} }
return ok; return ok;
@@ -103,6 +213,21 @@ Singleton {
if (state.omitEndConnector !== undefined) if (state.omitEndConnector !== undefined)
popoutOmitEndConnector = !!state.omitEndConnector; popoutOmitEndConnector = !!state.omitEndConnector;
_setSurfaceDescriptor(popoutScreen, "popout", Object.assign({}, state, {
"kind": "popout",
"screenName": popoutScreen,
"visible": popoutVisible,
"presented": state.presented !== undefined ? !!state.presented : popoutVisible,
"barSide": popoutBarSide,
"bodyX": popoutBodyX,
"bodyY": popoutBodyY,
"bodyW": popoutBodyW,
"bodyH": popoutBodyH,
"animX": popoutAnimX,
"animY": popoutAnimY,
"omitStartConnector": popoutOmitStartConnector,
"omitEndConnector": popoutOmitEndConnector
}), claimId);
return true; return true;
} }
@@ -123,6 +248,7 @@ Singleton {
popoutScreen = ""; popoutScreen = "";
popoutOmitStartConnector = false; popoutOmitStartConnector = false;
popoutOmitEndConnector = false; popoutOmitEndConnector = false;
_clearSurfaceDescriptor(releasedScreen, "popout", claimId);
_bumpSurfaceRevision(releasedScreen); _bumpSurfaceRevision(releasedScreen);
return true; return true;
} }
@@ -193,13 +319,21 @@ Singleton {
return false; return false;
const normalized = _normalizeDockState(state); const normalized = _normalizeDockState(state);
if (_sameDockState(dockStates[screenName], normalized)) const descriptorState = Object.assign({}, state, normalized, {
return true; "kind": "dock",
"screenName": screenName,
"visible": normalized.reveal,
"presented": normalized.reveal,
"phase": normalized.reveal ? (state.phase || "open") : "hidden"
});
const previous = dockStates[screenName] || emptyDockState; const previous = dockStates[screenName] || emptyDockState;
const stateChanged = !_sameDockState(dockStates[screenName], normalized);
if (stateChanged) {
const next = _cloneDict(dockStates); const next = _cloneDict(dockStates);
next[screenName] = normalized; next[screenName] = normalized;
dockStates = next; dockStates = next;
}
_setSurfaceDescriptor(screenName, "dock", descriptorState, "dock:" + screenName);
if (!!previous.reveal !== !!normalized.reveal) if (!!previous.reveal !== !!normalized.reveal)
_bumpSurfaceRevision(screenName); _bumpSurfaceRevision(screenName);
return true; return true;
@@ -212,8 +346,8 @@ Singleton {
const next = _cloneDict(dockStates); const next = _cloneDict(dockStates);
delete next[screenName]; delete next[screenName];
dockStates = next; dockStates = next;
_clearSurfaceDescriptor(screenName, "dock");
// Also clear corresponding slide
if (dockSlides[screenName]) { if (dockSlides[screenName]) {
const nextSlides = _cloneDict(dockSlides); const nextSlides = _cloneDict(dockSlides);
delete nextSlides[screenName]; delete nextSlides[screenName];
@@ -283,13 +417,20 @@ Singleton {
return false; return false;
const normalized = _normalizeNotificationState(state); const normalized = _normalizeNotificationState(state);
if (_sameNotificationState(notificationStates[screenName], normalized)) const descriptorState = Object.assign({}, state, normalized, {
return true; "kind": "notification",
"screenName": screenName,
"presented": normalized.visible,
"phase": normalized.visible ? (state.phase || "open") : "hidden"
});
const previous = notificationStates[screenName] || emptyNotificationState; const previous = notificationStates[screenName] || emptyNotificationState;
const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized);
if (stateChanged) {
const next = _cloneDict(notificationStates); const next = _cloneDict(notificationStates);
next[screenName] = normalized; next[screenName] = normalized;
notificationStates = next; notificationStates = next;
}
_setSurfaceDescriptor(screenName, "notification", descriptorState, "notification:" + screenName);
if (!!previous.visible !== !!normalized.visible) if (!!previous.visible !== !!normalized.visible)
_bumpSurfaceRevision(screenName); _bumpSurfaceRevision(screenName);
return true; return true;
@@ -302,11 +443,11 @@ Singleton {
const next = _cloneDict(notificationStates); const next = _cloneDict(notificationStates);
delete next[screenName]; delete next[screenName];
notificationStates = next; notificationStates = next;
_clearSurfaceDescriptor(screenName, "notification");
_bumpSurfaceRevision(screenName); _bumpSurfaceRevision(screenName);
return true; return true;
} }
// DankModal / DankLauncherV2Modal State
readonly property var emptyModalState: ({ readonly property var emptyModalState: ({
"visible": false, "visible": false,
"barSide": "bottom", "barSide": "bottom",
@@ -362,6 +503,10 @@ Singleton {
const next = _cloneDict(modalStates); const next = _cloneDict(modalStates);
next[screenName] = normalized; next[screenName] = normalized;
modalStates = next; modalStates = next;
_setSurfaceDescriptor(screenName, "modal", Object.assign({}, state, normalized, {
"kind": state.kind || "modal",
"screenName": screenName
}), ownerId || "");
_bumpSurfaceRevision(screenName); _bumpSurfaceRevision(screenName);
return true; return true;
} }
@@ -372,11 +517,16 @@ Singleton {
if (ownerId && modalOwners[screenName] !== ownerId) if (ownerId && modalOwners[screenName] !== ownerId)
return false; return false;
const normalized = _normalizeModalState(state); const normalized = _normalizeModalState(state);
if (_sameModalState(modalStates[screenName], normalized)) const descriptorState = Object.assign({}, state, normalized, {
return true; "kind": state.kind || (surfaceDescriptor(screenName, "modal").kind || "modal"),
"screenName": screenName
});
if (!_sameModalState(modalStates[screenName], normalized)) {
const next = _cloneDict(modalStates); const next = _cloneDict(modalStates);
next[screenName] = normalized; next[screenName] = normalized;
modalStates = next; modalStates = next;
}
_setSurfaceDescriptor(screenName, "modal", descriptorState, ownerId || modalOwners[screenName] || "");
return true; return true;
} }
@@ -395,10 +545,6 @@ Singleton {
return updateModalState(screenName, state, ownerId); return updateModalState(screenName, state, ownerId);
} }
function setModalState(screenName, state) {
return updateModalState(screenName, state, null);
}
function clearModalState(screenName, ownerId) { function clearModalState(screenName, ownerId) {
if (!screenName) if (!screenName)
return false; return false;
@@ -418,6 +564,7 @@ Singleton {
delete nextOwners[screenName]; delete nextOwners[screenName];
modalOwners = nextOwners; modalOwners = nextOwners;
} }
_clearSurfaceDescriptor(screenName, "modal", ownerId);
_bumpSurfaceRevision(screenName); _bumpSurfaceRevision(screenName);
return true; return true;
} }
@@ -501,9 +648,6 @@ Singleton {
return false; return false;
} }
// Prune state for screens that are no longer connected. Stale entries
// accumulate across hotplug cycles otherwise — Frame's per-screen
// FrameInstance doesn't notice when its peer dicts go orphan.
function _pruneToLiveScreens() { function _pruneToLiveScreens() {
const live = {}; const live = {};
const screens = Quickshell.screens || []; const screens = Quickshell.screens || [];
@@ -543,6 +687,9 @@ Singleton {
const nextSurfaceRevisions = pruneKeyed(surfaceRevisions); const nextSurfaceRevisions = pruneKeyed(surfaceRevisions);
if (nextSurfaceRevisions !== null) if (nextSurfaceRevisions !== null)
surfaceRevisions = nextSurfaceRevisions; surfaceRevisions = nextSurfaceRevisions;
const nextDescriptors = pruneKeyed(surfaceDescriptors);
if (nextDescriptors !== null)
surfaceDescriptors = nextDescriptors;
let retractChanged = false; let retractChanged = false;
const nextRetract = {}; const nextRetract = {};
@@ -0,0 +1,159 @@
.pragma library
var VALID_KINDS = {
"popout": true,
"modal": true,
"launcher": true,
"dock": true,
"notification": true
};
var VALID_PHASES = {
"opening": true,
"open": true,
"closing": true,
"hidden": true,
"recovering": true
};
function _number(value, fallback) {
var n = Number(value);
return isNaN(n) ? fallback : n;
}
function _bool(value, fallback) {
return value === undefined ? fallback : !!value;
}
function _kind(value, fallback) {
if (VALID_KINDS[value])
return value;
return VALID_KINDS[fallback] ? fallback : "modal";
}
function _defaultBarSide(kind) {
return kind === "popout" || kind === "notification" ? "top" : "bottom";
}
function _barSide(value, fallback) {
if (value === "top" || value === "bottom" || value === "left" || value === "right")
return value;
return fallback;
}
function slotForKind(kind) {
return kind === "launcher" ? "modal" : _kind(kind, "modal");
}
function inferPhase(visible, presented, requestedPhase) {
if (VALID_PHASES[requestedPhase])
return requestedPhase;
if (!visible && !presented)
return "hidden";
if (!visible && presented)
return "closing";
return "open";
}
function normalize(input, defaults) {
var source = input || {};
var base = defaults || {};
var kind = _kind(source.kind, base.kind);
var defaultSide = _defaultBarSide(kind);
var sourceRect = source.bodyRect || {};
var baseRect = base.bodyRect || {};
var sourceOffset = source.animationOffset || {};
var baseOffset = base.animationOffset || {};
var visible = _bool(source.visible !== undefined ? source.visible : source.reveal, _bool(base.visible !== undefined ? base.visible : base.reveal, false));
var presented = _bool(source.presented, _bool(base.presented, visible));
var bodyRect = {
"x": _number(sourceRect.x !== undefined ? sourceRect.x : source.bodyX, _number(baseRect.x !== undefined ? baseRect.x : base.bodyX, 0)),
"y": _number(sourceRect.y !== undefined ? sourceRect.y : source.bodyY, _number(baseRect.y !== undefined ? baseRect.y : base.bodyY, 0)),
"width": Math.max(0, _number(sourceRect.width !== undefined ? sourceRect.width : source.bodyW, _number(baseRect.width !== undefined ? baseRect.width : base.bodyW, 0))),
"height": Math.max(0, _number(sourceRect.height !== undefined ? sourceRect.height : source.bodyH, _number(baseRect.height !== undefined ? baseRect.height : base.bodyH, 0)))
};
var animationOffset = {
"x": _number(sourceOffset.x !== undefined ? sourceOffset.x : (source.animX !== undefined ? source.animX : source.slideX), _number(baseOffset.x !== undefined ? baseOffset.x : (base.animX !== undefined ? base.animX : base.slideX), 0)),
"y": _number(sourceOffset.y !== undefined ? sourceOffset.y : (source.animY !== undefined ? source.animY : source.slideY), _number(baseOffset.y !== undefined ? baseOffset.y : (base.animY !== undefined ? base.animY : base.slideY), 0))
};
var screenName = source.screenName !== undefined ? source.screenName : (source.screen !== undefined ? source.screen : (base.screenName !== undefined ? base.screenName : base.screen));
var opacity = Math.max(0, Math.min(1, _number(source.opacity, _number(base.opacity, 1))));
return {
"ownerId": String(source.ownerId !== undefined ? source.ownerId : (base.ownerId || "")),
"kind": kind,
"screenName": String(screenName || ""),
"phase": inferPhase(visible, presented, source.phase !== undefined ? source.phase : base.phase),
"visible": visible,
"presented": presented,
"barSide": _barSide(source.barSide, _barSide(base.barSide, defaultSide)),
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": Math.max(0, _number(source.scale, _number(base.scale, 1))),
"opacity": opacity,
"omitStartConnector": _bool(source.omitStartConnector, _bool(base.omitStartConnector, false)),
"omitEndConnector": _bool(source.omitEndConnector, _bool(base.omitEndConnector, false)),
"dockRetractSide": String(source.dockRetractSide !== undefined ? source.dockRetractSide : (base.dockRetractSide || "")),
"revision": Math.max(0, Math.floor(_number(source.revision, _number(base.revision, 0))))
};
}
function empty(kind, screenName) {
return normalize({
"kind": kind,
"screenName": screenName || "",
"phase": "hidden",
"visible": false,
"presented": false
});
}
function withRevision(descriptor, revision) {
var next = normalize(descriptor);
next.revision = Math.max(0, Math.floor(_number(revision, next.revision)));
return next;
}
function withAnimationOffset(descriptor, x, y) {
var next = normalize(descriptor);
next.animationOffset = {
"x": x === undefined ? next.animationOffset.x : _number(x, next.animationOffset.x),
"y": y === undefined ? next.animationOffset.y : _number(y, next.animationOffset.y)
};
return next;
}
function withBodyRect(descriptor, x, y, width, height) {
var next = normalize(descriptor);
next.bodyRect = {
"x": x === undefined ? next.bodyRect.x : _number(x, next.bodyRect.x),
"y": y === undefined ? next.bodyRect.y : _number(y, next.bodyRect.y),
"width": width === undefined ? next.bodyRect.width : Math.max(0, _number(width, next.bodyRect.width)),
"height": height === undefined ? next.bodyRect.height : Math.max(0, _number(height, next.bodyRect.height))
};
return next;
}
function same(a, b, threshold) {
if (!a || !b)
return false;
var epsilon = threshold === undefined ? 0.5 : Math.max(0, Number(threshold));
return a.ownerId === b.ownerId
&& a.kind === b.kind
&& a.screenName === b.screenName
&& a.phase === b.phase
&& a.visible === b.visible
&& a.presented === b.presented
&& a.barSide === b.barSide
&& Math.abs(a.bodyRect.x - b.bodyRect.x) < epsilon
&& Math.abs(a.bodyRect.y - b.bodyRect.y) < epsilon
&& Math.abs(a.bodyRect.width - b.bodyRect.width) < epsilon
&& Math.abs(a.bodyRect.height - b.bodyRect.height) < epsilon
&& Math.abs(a.animationOffset.x - b.animationOffset.x) < epsilon
&& Math.abs(a.animationOffset.y - b.animationOffset.y) < epsilon
&& Math.abs(a.scale - b.scale) < 0.0001
&& Math.abs(a.opacity - b.opacity) < 0.0001
&& a.omitStartConnector === b.omitStartConnector
&& a.omitEndConnector === b.omitEndConnector
&& a.dockRetractSide === b.dockRetractSide;
}
@@ -0,0 +1,232 @@
.pragma library
function _number(value, fallback) {
var n = Number(value);
return isNaN(n) ? fallback : n;
}
function snap(value, dpr) {
var scale = dpr || 1;
return Math.round(_number(value, 0) * scale) / scale;
}
function isHorizontal(side) {
return side === "top" || side === "bottom";
}
function isVertical(side) {
return side === "left" || side === "right";
}
function bodyRect(descriptor, dpr) {
var source = descriptor && descriptor.bodyRect ? descriptor.bodyRect : descriptor || {};
return {
"x": snap(source.x !== undefined ? source.x : source.bodyX, dpr),
"y": snap(source.y !== undefined ? source.y : source.bodyY, dpr),
"width": Math.max(0, snap(source.width !== undefined ? source.width : source.bodyW, dpr)),
"height": Math.max(0, snap(source.height !== undefined ? source.height : source.bodyH, dpr))
};
}
function animatedBodyRect(descriptor, dpr) {
var rect = bodyRect(descriptor, dpr);
var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : descriptor || {};
var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom";
var dx = isVertical(side) ? Math.max(-rect.width, Math.min(_number(offset.x !== undefined ? offset.x : offset.animX, 0), rect.width)) : 0;
var dy = isHorizontal(side) ? Math.max(-rect.height, Math.min(_number(offset.y !== undefined ? offset.y : offset.animY, 0), rect.height)) : 0;
return {
"x": snap(rect.x + (side === "right" ? dx : 0), dpr),
"y": snap(rect.y + (side === "bottom" ? dy : 0), dpr),
"width": Math.max(0, snap(rect.width - Math.abs(dx), dpr)),
"height": Math.max(0, snap(rect.height - Math.abs(dy), dpr)),
"dx": snap(dx, dpr),
"dy": snap(dy, dpr)
};
}
function translatedBodyRect(descriptor, dpr) {
var rect = bodyRect(descriptor, dpr);
var offset = descriptor && descriptor.animationOffset ? descriptor.animationOffset : {};
return {
"x": snap(rect.x + _number(offset.x, 0), dpr),
"y": snap(rect.y + _number(offset.y, 0), dpr),
"width": rect.width,
"height": rect.height
};
}
function connectorRadii(descriptor, rect, connectedRadius, surfaceRadius, dpr, nearIncludesSurface) {
var side = descriptor && descriptor.barSide ? descriptor.barSide : "bottom";
var horizontal = isHorizontal(side);
var extent = horizontal ? rect.height : rect.width;
var crossSize = horizontal ? rect.width : rect.height;
var nearLimit = nearIncludesSurface ? Math.min(connectedRadius, surfaceRadius, extent, crossSize / 2) : Math.min(connectedRadius, extent, crossSize / 2);
var farLimit = Math.min(connectedRadius, surfaceRadius, crossSize / 2);
var near = snap(Math.max(0, nearLimit), dpr);
var far = snap(Math.max(0, farLimit), dpr);
var omitStart = !!(descriptor && descriptor.omitStartConnector);
var omitEnd = !!(descriptor && descriptor.omitEndConnector);
return {
"near": near,
"far": far,
"start": omitStart ? 0 : near,
"end": omitEnd ? 0 : near,
"farStart": omitStart ? far : 0,
"farEnd": omitEnd ? far : 0,
"farExtent": Math.max(omitStart ? far : 0, omitEnd ? far : 0)
};
}
function _connectorWidth(side, spacing, radius) {
return isVertical(side) ? spacing + radius : radius;
}
function _connectorHeight(side, spacing, radius) {
return isVertical(side) ? radius : spacing + radius;
}
function connectorRect(side, rect, placement, spacing, radius, dpr) {
var width = _connectorWidth(side, spacing, radius);
var height = _connectorHeight(side, spacing, radius);
var seamX = isVertical(side) ? (side === "left" ? rect.x : rect.x + rect.width) : (placement === "left" ? rect.x : rect.x + rect.width);
var seamY = side === "top" ? rect.y : (side === "bottom" ? rect.y + rect.height : (placement === "left" ? rect.y : rect.y + rect.height));
var x = isVertical(side) ? (side === "left" ? seamX : seamX - width) : (placement === "left" ? seamX - width : seamX);
var y = side === "top" ? seamY : (side === "bottom" ? seamY - height : (placement === "left" ? seamY - height : seamY));
return {
"x": snap(x, dpr),
"y": snap(y, dpr),
"width": Math.max(0, snap(width, dpr)),
"height": Math.max(0, snap(height, dpr))
};
}
function farConnectorRect(side, rect, placement, radius, dpr) {
var x;
var y;
if (isHorizontal(side)) {
x = placement === "left" ? rect.x : rect.x + rect.width - radius;
y = side === "top" ? rect.y + rect.height : rect.y - radius;
} else {
x = side === "left" ? rect.x + rect.width : rect.x - radius;
y = placement === "left" ? rect.y : rect.y + rect.height - radius;
}
return {
"x": snap(x, dpr),
"y": snap(y, dpr),
"width": Math.max(0, snap(radius, dpr)),
"height": Math.max(0, snap(radius, dpr))
};
}
function farBodyCapRect(side, rect, placement, radius, dpr) {
var x;
var y;
if (isHorizontal(side)) {
x = placement === "left" ? rect.x : rect.x + rect.width - radius;
y = side === "top" ? rect.y + rect.height - radius : rect.y;
} else {
x = side === "left" ? rect.x + rect.width - radius : rect.x;
y = placement === "left" ? rect.y : rect.y + rect.height - radius;
}
return {
"x": snap(x, dpr),
"y": snap(y, dpr),
"width": Math.max(0, snap(radius, dpr)),
"height": Math.max(0, snap(radius, dpr))
};
}
function chromeBounds(rect, side, startRadius, endRadius, farExtent, dpr) {
var horizontal = isHorizontal(side);
var bodyOffsetX = horizontal ? startRadius : (side === "right" ? farExtent : 0);
var bodyOffsetY = horizontal ? (side === "bottom" ? farExtent : 0) : startRadius;
return {
"x": snap(rect.x - bodyOffsetX, dpr),
"y": snap(rect.y - bodyOffsetY, dpr),
"width": Math.max(0, snap(horizontal ? rect.width + startRadius + endRadius : rect.width + farExtent, dpr)),
"height": Math.max(0, snap(horizontal ? rect.height + farExtent : rect.height + startRadius + endRadius, dpr)),
"bodyOffsetX": snap(bodyOffsetX, dpr),
"bodyOffsetY": snap(bodyOffsetY, dpr)
};
}
function fillBounds(rect, side, seamOverlap, dpr) {
var overlapX = isHorizontal(side) ? seamOverlap : 0;
var overlapY = isVertical(side) ? seamOverlap : 0;
return {
"x": snap(rect.x - overlapX, dpr),
"y": snap(rect.y - overlapY, dpr),
"width": Math.max(0, snap(rect.width + overlapX * 2, dpr)),
"height": Math.max(0, snap(rect.height + overlapY * 2, dpr))
};
}
function clipEnvelope(rect, side, radii, seamOverlap, dpr) {
var fill = fillBounds(rect, side, seamOverlap, dpr);
var chrome = chromeBounds(fill, side, radii.start, radii.end, radii.farExtent, dpr);
return {
"x": chrome.x,
"y": chrome.y,
"width": chrome.width,
"height": chrome.height,
"bodyX": snap(fill.x - chrome.x, dpr),
"bodyY": snap(fill.y - chrome.y, dpr),
"bodyWidth": fill.width,
"bodyHeight": fill.height
};
}
function blurRegions(descriptor, rect, radii, dpr) {
var side = descriptor.barSide;
var regions = [bodyRect(rect, dpr)];
if (radii.start > 0)
regions.push(connectorRect(side, rect, "left", 0, radii.start, dpr));
if (radii.end > 0)
regions.push(connectorRect(side, rect, "right", 0, radii.end, dpr));
if (radii.farStart > 0) {
regions.push(farConnectorRect(side, rect, "left", radii.farStart, dpr));
regions.push(farBodyCapRect(side, rect, "left", radii.farStart, dpr));
}
if (radii.farEnd > 0) {
regions.push(farConnectorRect(side, rect, "right", radii.farEnd, dpr));
regions.push(farBodyCapRect(side, rect, "right", radii.farEnd, dpr));
}
return regions;
}
function unionBounds(rects, padding, dpr) {
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = 0; i < rects.length; i++) {
var rect = rects[i];
if (!rect || rect.width <= 0 || rect.height <= 0)
continue;
minX = Math.min(minX, rect.x);
minY = Math.min(minY, rect.y);
maxX = Math.max(maxX, rect.x + rect.width);
maxY = Math.max(maxY, rect.y + rect.height);
}
if (minX === Infinity)
return {"x": 0, "y": 0, "width": 0, "height": 0};
var pad = Math.max(0, _number(padding, 0));
return {
"x": snap(minX - pad, dpr),
"y": snap(minY - pad, dpr),
"width": Math.max(0, snap(maxX - minX + pad * 2, dpr)),
"height": Math.max(0, snap(maxY - minY + pad * 2, dpr))
};
}
function shadowSourceBounds(descriptor, rect, radii, padding, dpr) {
return unionBounds(blurRegions(descriptor, rect, radii, dpr), padding, dpr);
}
function stableEqual(a, b, dpr) {
if (!a || !b)
return false;
var threshold = 0.5 / (dpr || 1);
return Math.abs(a.x - b.x) < threshold && Math.abs(a.y - b.y) < threshold && Math.abs(a.width - b.width) < threshold && Math.abs(a.height - b.height) < threshold;
}
+176
View File
@@ -0,0 +1,176 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
required property string claimPrefix
required property var isCurrentOwner
required property var hasOwner
required property var claimState
required property var ensureState
required property var releaseState
property var statePresent: null
property var updateAnimationState: null
property var updateBodyState: null
property var requestDockRetract: null
property var releaseDockRetract: null
property string screenName: ""
property bool enabled: false
property bool active: false
property bool presented: false
property bool dockBlocked: false
property string dockSide: ""
property bool renewTokenOnRecovery: true
property string claimId: ""
property string claimedScreenName: ""
property int _claimSerial: 0
signal recoveryRequested
visible: false
function _nextClaimId() {
_claimSerial += 1;
return claimPrefix + ":" + (new Date()).getTime() + ":" + _claimSerial + ":" + Math.floor(Math.random() * 1000000);
}
function _isCurrent(name) {
return !!name && !!isCurrentOwner && !!isCurrentOwner(name);
}
function _hasOwner(name, ownerId) {
return !!name && !!ownerId && !!hasOwner && !!hasOwner(name, ownerId);
}
function _hasState(name, ownerId) {
return !statePresent || !!statePresent(name, ownerId);
}
function _shouldRecover() {
return active && enabled && _isCurrent(screenName);
}
function requestRecovery() {
if (!_shouldRecover())
return false;
recoveryRequested();
return true;
}
function checkOwnershipRecovery() {
if (!_shouldRecover())
return false;
if (claimedScreenName === screenName && _hasOwner(screenName, claimId))
return false;
recoveryRequested();
return true;
}
function checkStateRecovery() {
if (!_shouldRecover())
return false;
if (claimedScreenName === screenName && _hasOwner(screenName, claimId) && _hasState(screenName, claimId))
return false;
recoveryRequested();
return true;
}
function checkRecovery() {
return checkStateRecovery();
}
function beginClaim() {
if (claimId && releaseDockRetract)
releaseDockRetract(claimId);
claimId = _nextClaimId();
claimedScreenName = "";
return claimId;
}
function _syncDockRetract() {
if (!claimId)
return;
if (dockBlocked && presented && dockSide && requestDockRetract)
requestDockRetract(claimId, screenName, dockSide);
else if (releaseDockRetract)
releaseDockRetract(claimId);
}
function publish(state, forceClaim) {
if (!enabled || !screenName || !state) {
release();
return false;
}
if (claimedScreenName && claimedScreenName !== screenName)
release();
const current = _isCurrent(screenName);
let claiming = !!forceClaim || !claimId;
if (claiming && !current)
return false;
if (!claimId)
beginClaim();
let published = claiming ? claimState(screenName, state, claimId) : ensureState(screenName, state, claimId);
if (!published && !claiming && current) {
if (renewTokenOnRecovery) {
beginClaim();
} else if (releaseDockRetract) {
releaseDockRetract(claimId);
}
published = claimState(screenName, state, claimId);
}
if (!published)
return false;
claimedScreenName = screenName;
_syncDockRetract();
return true;
}
function updateAnim(animX, animY) {
if (!enabled || !claimId || !claimedScreenName || !updateAnimationState)
return false;
if (!_hasOwner(claimedScreenName, claimId)) {
requestRecovery();
return false;
}
return updateAnimationState(claimedScreenName, claimId, animX, animY);
}
function updateBody(bodyX, bodyY, bodyW, bodyH) {
if (!enabled || !claimId || !claimedScreenName || !updateBodyState)
return false;
if (!_hasOwner(claimedScreenName, claimId)) {
requestRecovery();
return false;
}
return updateBodyState(claimedScreenName, claimId, bodyX, bodyY, bodyW, bodyH);
}
function release() {
if (!claimId) {
claimedScreenName = "";
return false;
}
const releasedClaimId = claimId;
const releasedScreenName = claimedScreenName;
claimId = "";
claimedScreenName = "";
if (releaseDockRetract)
releaseDockRetract(releasedClaimId);
if (releasedScreenName)
return !!releaseState(releasedScreenName, releasedClaimId);
return false;
}
Component.onDestruction: release()
}
+21 -30
View File
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import qs.Common import qs.Common
Item { Item {
@@ -19,7 +18,11 @@ Item {
property real bottomRightRadius: targetRadius property real bottomRightRadius: targetRadius
property color borderColor: "transparent" property color borderColor: "transparent"
property real borderWidth: 0 property real borderWidth: 0
property bool useCustomSource: false
property real sourceX: 0
property real sourceY: 0
property real sourceWidth: width
property real sourceHeight: height
property bool shadowEnabled: Theme.elevationEnabled property bool shadowEnabled: Theme.elevationEnabled
property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0 property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
@@ -28,36 +31,24 @@ Item {
property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset) property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
property color shadowColor: Theme.elevationShadowColor(level) property color shadowColor: Theme.elevationShadowColor(level)
property real shadowOpacity: 1 property real shadowOpacity: 1
property real blurMax: Theme.elevationBlurMax
property alias sourceRect: sourceRect readonly property var _ambient: Theme.elevationAmbient(level)
readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0
layer.enabled: shadowEnabled ShaderEffect {
layer.effect: MultiEffect {
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax)))
shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height))
shadowHorizontalOffset: root.shadowOffsetX
shadowVerticalOffset: root.shadowOffsetY
blurMax: root.blurMax
shadowColor: root.shadowColor
shadowOpacity: root.shadowOpacity
}
Rectangle {
id: sourceRect
anchors.fill: parent anchors.fill: parent
visible: !root.useCustomSource anchors.margins: -root._pad
topLeftRadius: root.topLeftRadius fragmentShader: Qt.resolvedUrl("../Shaders/qsb/elevation_rect.frag.qsb")
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius property real widthPx: width
bottomRightRadius: root.bottomRightRadius property real heightPx: height
color: root.targetColor property real borderWidth: root.borderWidth
border.color: root.borderColor property vector4d rectPx: Qt.vector4d(root._pad + root.sourceX, root._pad + root.sourceY, root.sourceWidth, root.sourceHeight)
border.width: root.borderWidth property vector4d cornerRadius: Qt.vector4d(root.topLeftRadius, root.topRightRadius, root.bottomRightRadius, root.bottomLeftRadius)
property vector4d fillColor: Qt.vector4d(root.targetColor.r, root.targetColor.g, root.targetColor.b, root.targetColor.a)
property vector4d borderColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a)
property vector4d shadowColor: Qt.vector4d(root.shadowColor.r, root.shadowColor.g, root.shadowColor.b, root.shadowEnabled ? root.shadowColor.a * root.shadowOpacity : 0)
property vector4d shadowParam: Qt.vector4d(Math.max(0, root.shadowBlurPx), root.shadowSpreadPx, root.shadowOffsetX, root.shadowOffsetY)
property vector4d ambientParam: Qt.vector4d(root._ambient.blurPx, root._ambient.spreadPx, root.shadowEnabled ? root._ambient.alpha * root.shadowOpacity : 0, 0)
} }
} }
+44
View File
@@ -0,0 +1,44 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Services
// Manages keyboard focus policy for popouts, modals, and Hyprland focus grabs
Singleton {
id: root
function keyboardFocus(active, customFocus) {
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (customFocus !== null && customFocus !== undefined)
return customFocus;
if (!active)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
function wantsGrab(active, customFocus) {
return CompositorService.useHyprlandFocusGrab && keyboardFocus(active, customFocus) === WlrKeyboardFocus.OnDemand;
}
property list<var> barWindows: []
function registerBarWindow(window) {
if (!window || barWindows.indexOf(window) !== -1)
return;
barWindows = barWindows.concat([window]);
}
function unregisterBarWindow(window) {
const idx = barWindows.indexOf(window);
if (idx === -1)
return;
const next = barWindows.slice();
next.splice(idx, 1);
barWindows = next;
}
}
+10
View File
@@ -108,6 +108,7 @@ Singleton {
} }
property bool clipboardEnterToPaste: false property bool clipboardEnterToPaste: false
property var clipboardVisibleEntryActions: ["pin", "edit", "delete"]
property var launcherPluginVisibility: ({}) property var launcherPluginVisibility: ({})
@@ -1650,6 +1651,15 @@ Singleton {
}; };
} }
function effectiveBarConfigForRender(config, usesFrameBarChrome) {
if (!config || !connectedFrameModeActive || usesFrameBarChrome)
return config;
const backup = connectedFrameBarStyleBackups[config.id];
if (!backup)
return config;
return Object.assign({}, config, backup);
}
// Single entry point for connected-mode settings state. // Single entry point for connected-mode settings state.
// !active → restore backups // !active → restore backups
function _reconcileConnectedFrameBarStyles() { function _reconcileConnectedFrameBarStyles() {
+10
View File
@@ -911,6 +911,16 @@ Singleton {
} }
return Qt.rgba(r, g, b, alpha); return Qt.rgba(r, g, b, alpha);
} }
function elevationAmbient(level) {
const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0;
const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5;
return {
blurPx: blur * 1.75,
spreadPx: 1,
alpha: alpha
};
}
function elevationTintOpacity(level) { function elevationTintOpacity(level) {
if (!level) if (!level)
return 0; return 0;
@@ -572,6 +572,7 @@ var SPEC = {
builtInPluginSettings: { def: {} }, builtInPluginSettings: { def: {} },
clipboardEnterToPaste: { def: false }, clipboardEnterToPaste: { def: false },
clipboardVisibleEntryActions: { def: ["pin", "edit", "delete"] },
launcherPluginVisibility: { def: {} }, launcherPluginVisibility: { def: {} },
launcherPluginOrder: { def: [] }, launcherPluginOrder: { def: [] },
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:bluetooth-pairing" layerNamespace: "dms:bluetooth-pairing"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property string deviceName: "" property string deviceName: ""
property string deviceAddress: "" property string deviceAddress: ""
property string requestType: "" property string requestType: ""
@@ -7,7 +7,6 @@ Item {
id: clipboardContent id: clipboardContent
required property var modal required property var modal
required property var clearConfirmDialog
property alias searchField: searchField property alias searchField: searchField
property alias clipboardListView: clipboardListView property alias clipboardListView: clipboardListView
@@ -33,14 +32,7 @@ Item {
pinnedCount: modal.pinnedCount pinnedCount: modal.pinnedCount
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onTabChanged: tabName => modal.activeTab = tabName onTabChanged: tabName => modal.activeTab = tabName
onClearAllClicked: { onClearAllClicked: modal.confirmClearAll()
const hasPinned = modal.pinnedCount > 0;
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(modal.pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
modal.clearAll();
modal.hide();
}, function () {});
}
onCloseClicked: modal.hide() onCloseClicked: modal.hide()
} }
+35 -7
View File
@@ -22,7 +22,14 @@ Rectangle {
readonly property string entryType: modal ? modal.getEntryType(entry) : "text" readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : "" readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
readonly property var pinnedDuplicateEntry: !entry.pinned ? ClipboardService.getPinnedEntryByHash(entry.hash) : null readonly property var pinnedDuplicateEntry: !entry.pinned ? ClipboardService.getPinnedEntryByHash(entry.hash) : null
readonly property bool effectivePinned: entry.pinned || pinnedDuplicateEntry !== null readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null
readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate
readonly property var visibleEntryActions: SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"]
readonly property bool showPinAction: visibleEntryActions.includes("pin")
readonly property bool showEditAction: visibleEntryActions.includes("edit")
readonly property bool showDeleteAction: visibleEntryActions.includes("delete")
readonly property bool showPinnedIndicator: !showPinAction && effectivePinned
readonly property bool showAnyAction: showPinAction || showEditAction || showDeleteAction || showPinnedIndicator
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
@@ -63,12 +70,31 @@ Rectangle {
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: root.showAnyAction
DankActionButton { DankActionButton {
iconName: "push_pin" iconName: "push_pin"
iconSize: Theme.iconSize - 6 iconSize: Theme.iconSize - 6
iconColor: effectivePinned ? Theme.primary : Theme.surfaceText iconColor: Theme.primary
backgroundColor: effectivePinned ? Theme.primarySelected : "transparent" backgroundColor: Theme.primarySelected
visible: root.showPinnedIndicator
onClicked: {
if (entry.pinned) {
unpinRequested(entry);
return;
}
if (pinnedDuplicateEntry) {
unpinRequested(pinnedDuplicateEntry);
}
}
}
DankActionButton {
iconName: "push_pin"
iconSize: Theme.iconSize - 6
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
visible: root.showPinAction
onClicked: { onClicked: {
if (entry.pinned) { if (entry.pinned) {
unpinRequested(entry); unpinRequested(entry);
@@ -86,6 +112,7 @@ Rectangle {
iconName: "edit" iconName: "edit"
iconSize: Theme.iconSize - 6 iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
visible: root.showEditAction
onClicked: { onClicked: {
if (entryType === "image") { if (entryType === "image") {
@@ -99,6 +126,7 @@ Rectangle {
iconName: "close" iconName: "close"
iconSize: Theme.iconSize - 6 iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
visible: root.showDeleteAction
onClicked: deleteRequested() onClicked: deleteRequested()
} }
} }
@@ -106,8 +134,8 @@ Rectangle {
Item { Item {
anchors.left: indexBadge.right anchors.left: indexBadge.right
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.right: actionButtons.left anchors.right: root.showAnyAction ? actionButtons.left : parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: root.showAnyAction ? Theme.spacingM : Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// height: contentColumn.implicitHeight // height: contentColumn.implicitHeight
height: ClipboardConstants.itemHeight height: ClipboardConstants.itemHeight
@@ -168,8 +196,8 @@ Rectangle {
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.left: parent.left anchors.left: parent.left
anchors.right: actionButtons.left anchors.right: root.showAnyAction ? actionButtons.left : parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: root.showAnyAction ? Theme.spacingS : 0
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
hoverEnabled: true hoverEnabled: true
@@ -82,6 +82,15 @@ FocusScope {
ClipboardService.clearAll(); ClipboardService.clearAll();
} }
function confirmClearAll() {
const hasPinned = pinnedCount > 0;
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
clearAll();
hide();
}, function () {});
}
function getEntryPreview(entry) { function getEntryPreview(entry) {
return ClipboardService.getEntryPreview(entry); return ClipboardService.getEntryPreview(entry);
} }
@@ -135,7 +144,6 @@ FocusScope {
id: historyContent id: historyContent
anchors.fill: parent anchors.fill: parent
modal: root modal: root
clearConfirmDialog: root.clearConfirmDialog
} }
} }
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Clipboard import qs.Modals.Clipboard
import qs.Modals.Common import qs.Modals.Common
@@ -12,11 +11,6 @@ DankModal {
layerNamespace: "dms:clipboard" layerNamespace: "dms:clipboard"
HyprlandFocusGrab {
windows: [clipboardHistoryModal.contentWindow]
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
}
function toggle() { function toggle() {
if (shouldBeVisible) { if (shouldBeVisible) {
hide(); hide();
@@ -64,6 +58,7 @@ DankModal {
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
visible: false visible: false
keepContentLoaded: true
modalWidth: ClipboardConstants.modalWidth modalWidth: ClipboardConstants.modalWidth
modalHeight: ClipboardConstants.modalHeight modalHeight: ClipboardConstants.modalHeight
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
@@ -82,22 +77,35 @@ DankModal {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All") confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onVisibleChanged: { onShouldBeVisibleChanged: {
if (visible) { if (shouldBeVisible) {
clipboardHistoryModal.shouldHaveFocus = false; clipboardHistoryModal.shouldHaveFocus = false;
selectedButton = 0;
keyboardNavigation = true;
return; return;
} }
Qt.callLater(function () { Qt.callLater(function () {
if (!clipboardHistoryModal.shouldBeVisible) { if (!clipboardHistoryModal.shouldBeVisible) {
return; return;
} }
clipboardHistoryModal.shouldHaveFocus = true; clipboardHistoryModal.shouldHaveFocus = Qt.binding(() => clipboardHistoryModal.shouldBeVisible);
clipboardHistoryModal.modalFocusScope.forceActiveFocus(); clipboardHistoryModal.modalFocusScope.forceActiveFocus();
if (clipboardHistoryModal.contentLoader.item?.searchField) { if (clipboardHistoryModal.contentLoader.item?.searchField) {
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus(); clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
} }
}); });
} }
Connections {
target: clearConfirmDialog.modalFocusScope.Keys
function onPressed(event) {
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
return;
}
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
clearConfirmDialog.keyboardNavigation = true;
event.accepted = true;
}
}
} }
content: Component { content: Component {
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Modals.Clipboard import qs.Modals.Clipboard
import qs.Modals.Common import qs.Modals.Common
@@ -95,6 +96,35 @@ DankPopout {
id: clearConfirmDialog id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All") confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary confirmButtonColor: Theme.primary
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
root.customKeyboardFocus = WlrKeyboardFocus.None;
selectedButton = 0;
keyboardNavigation = true;
return;
}
root.customKeyboardFocus = null;
Qt.callLater(function () {
if (!root.shouldBeVisible || !root.contentLoader.item) {
return;
}
root.contentLoader.item.forceActiveFocus();
if (root.contentLoader.item.searchField) {
root.contentLoader.item.searchField.forceActiveFocus();
}
});
}
Connections {
target: clearConfirmDialog.modalFocusScope.Keys
function onPressed(event) {
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
return;
}
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
clearConfirmDialog.keyboardNavigation = true;
event.accepted = true;
}
}
} }
content: Component { content: Component {
@@ -125,8 +125,6 @@ QtObject {
if (!ClipboardService.keyboardNavigationActive) { if (!ClipboardService.keyboardNavigationActive) {
ClipboardService.keyboardNavigationActive = true; ClipboardService.keyboardNavigationActive = true;
ClipboardService.selectedIndex = 0; ClipboardService.selectedIndex = 0;
} else if (ClipboardService.selectedIndex === 0) {
ClipboardService.keyboardNavigationActive = false;
} else { } else {
selectPrevious(); selectPrevious();
} }
@@ -155,8 +153,6 @@ QtObject {
if (!ClipboardService.keyboardNavigationActive) { if (!ClipboardService.keyboardNavigationActive) {
ClipboardService.keyboardNavigationActive = true; ClipboardService.keyboardNavigationActive = true;
ClipboardService.selectedIndex = 0; ClipboardService.selectedIndex = 0;
} else if (ClipboardService.selectedIndex === 0) {
ClipboardService.keyboardNavigationActive = false;
} else { } else {
selectPrevious(); selectPrevious();
} }
@@ -184,8 +180,7 @@ QtObject {
if (event.modifiers & Qt.ShiftModifier) { if (event.modifiers & Qt.ShiftModifier) {
switch (event.key) { switch (event.key) {
case Qt.Key_Delete: case Qt.Key_Delete:
modal.clearAll(); modal.confirmClearAll();
modal.hide();
event.accepted = true; event.accepted = true;
return; return;
case Qt.Key_Return: case Qt.Key_Return:
+7 -3
View File
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -52,8 +53,13 @@ Item {
focus: true focus: true
anchors.fill: parent anchors.fill: parent
} }
// Hyprland OnDemand grab delivers keyboard focus to the modal content surface.
HyprlandFocusGrab {
windows: root.contentWindow ? [root.contentWindow] : []
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
}
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920 readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080 readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
@@ -96,8 +102,6 @@ Item {
} }
} }
// Defer Loader source-component swap until impl is fully closed; avoids
// tearing down a modal mid-animation when frame mode is toggled.
function _maybeResolveBackend() { function _maybeResolveBackend() {
if (_resolvedBackend === _desiredBackend) if (_resolvedBackend === _desiredBackend)
return; return;
+46 -121
View File
@@ -31,7 +31,6 @@ Item {
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 property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences) readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
@@ -87,16 +86,13 @@ Item {
property real frozenMotionOffsetX: 0 property real frozenMotionOffsetX: 0
property real frozenMotionOffsetY: 0 property real frozenMotionOffsetY: 0
readonly property alias contentWindow: contentWindow readonly property alias contentWindow: contentWindow
readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: false readonly property bool useBackground: false
readonly property bool useSingleWindow: CompositorService.isHyprland
signal opened signal opened
signal dialogClosed signal dialogClosed
signal backgroundClicked signal backgroundClicked
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer { Timer {
id: _syncTimer id: _syncTimer
interval: 0 interval: 0
@@ -115,6 +111,7 @@ Item {
id: modalChrome id: modalChrome
modalHandle: root.modalHandle modalHandle: root.modalHandle
claimPrefix: root.layerNamespace + ":modal" claimPrefix: root.layerNamespace + ":modal"
surfaceKind: "modal"
screenName: root._currentScreenName() screenName: root._currentScreenName()
enabled: root.frameOwnsConnectedChrome enabled: root.frameOwnsConnectedChrome
active: root.shouldBeVisible active: root.shouldBeVisible
@@ -125,17 +122,38 @@ Item {
} }
function _publishModalChromeState() { function _publishModalChromeState() {
const presented = shouldBeVisible || contentWindow.visible;
const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open"));
const bodyRect = {
"x": alignedX,
"y": alignedY,
"width": alignedWidth,
"height": alignedHeight
};
const animationOffset = {
"x": modalContainer ? modalContainer.animX : 0,
"y": modalContainer ? modalContainer.animY : 0
};
const state = { const state = {
"visible": shouldBeVisible || contentWindow.visible, "kind": "modal",
"screenName": root._currentScreenName(),
"phase": phase,
"visible": presented,
"presented": presented,
"barSide": resolvedConnectedBarSide, "barSide": resolvedConnectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": alignedX, "bodyX": alignedX,
"bodyY": alignedY, "bodyY": alignedY,
"bodyW": alignedWidth, "bodyW": alignedWidth,
"bodyH": alignedHeight, "bodyH": alignedHeight,
"animX": modalContainer ? modalContainer.animX : 0, "animX": animationOffset.x,
"animY": modalContainer ? modalContainer.animY : 0, "animY": animationOffset.y,
"omitStartConnector": false, "omitStartConnector": false,
"omitEndConnector": false "omitEndConnector": false,
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
}; };
return modalChrome.publish(state); return modalChrome.publish(state);
} }
@@ -222,22 +240,16 @@ Item {
const focusedScreen = CompositorService.getFocusedScreen(); const focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) { if (focusedScreen) {
contentWindow.screen = focusedScreen; contentWindow.screen = focusedScreen;
if (!useSingleWindow)
clickCatcher.screen = focusedScreen;
} }
ModalManager.openModal(modalHandle); ModalManager.openModal(modalHandle);
if (Theme.isDirectionalEffect || root.useBackground) { if (Theme.isDirectionalEffect || root.useBackground) {
if (!useSingleWindow)
clickCatcher.visible = true;
contentWindow.visible = true; contentWindow.visible = true;
} }
Qt.callLater(() => { Qt.callLater(() => {
animationsEnabled = true; animationsEnabled = true;
shouldBeVisible = true; shouldBeVisible = true;
if (!useSingleWindow && !clickCatcher.visible)
clickCatcher.visible = true;
if (!contentWindow.visible) if (!contentWindow.visible)
contentWindow.visible = true; contentWindow.visible = true;
opened(); opened();
@@ -264,8 +276,6 @@ Item {
ModalManager.closeModal(modalHandle); ModalManager.closeModal(modalHandle);
closeTimer.stop(); closeTimer.stop();
contentWindow.visible = false; contentWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed(); dialogClosed();
Qt.callLater(() => animationsEnabled = true); Qt.callLater(() => animationsEnabled = true);
} }
@@ -304,8 +314,6 @@ Item {
const newScreen = CompositorService.getFocusedScreen(); const newScreen = CompositorService.getFocusedScreen();
if (newScreen) { if (newScreen) {
contentWindow.screen = newScreen; contentWindow.screen = newScreen;
if (!useSingleWindow)
clickCatcher.screen = newScreen;
} }
} }
} }
@@ -317,29 +325,12 @@ Item {
if (shouldBeVisible) if (shouldBeVisible)
return; return;
contentWindow.visible = false; contentWindow.visible = false;
if (!useSingleWindow)
clickCatcher.visible = false;
dialogClosed(); dialogClosed();
} }
} }
// 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: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: {
if (frameOwnsConnectedChrome)
return 0;
if (animationType === "slide")
return 30;
if (Theme.isDirectionalEffect)
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
if (Theme.isDepthEffect)
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
return Math.max(0, animationOffset);
}
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
readonly property real alignedWidth: Theme.px(modalWidth, dpr) readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr) readonly property real alignedHeight: Theme.px(modalHeight, dpr)
@@ -349,7 +340,6 @@ Item {
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side); return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
} }
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedAlignedX: { readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) { switch (resolvedConnectedBarSide) {
case "top": case "top":
@@ -412,57 +402,6 @@ Item {
} }
})(), dpr) })(), dpr)
PanelWindow {
id: clickCatcher
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
item: Rectangle {
x: root.alignedX
y: root.alignedY
width: root.alignedWidth
height: root.alignedHeight
}
intersection: Intersection.Xor
}
MouseArea {
anchors.fill: parent
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: root.backgroundClicked()
}
Rectangle {
anchors.fill: parent
z: -1
color: "black"
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: opacity > 0
Behavior on opacity {
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
NumberAnimation {
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
}
PanelWindow { PanelWindow {
id: contentWindow id: contentWindow
visible: false visible: false
@@ -472,8 +411,8 @@ Item {
targetWindow: contentWindow targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome 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: connectedReveal.x + 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: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0 blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0 blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
blurRadius: root.effectiveCornerRadius blurRadius: root.effectiveCornerRadius
@@ -487,36 +426,15 @@ Item {
"error": true "error": true
}) })
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldHaveFocus)
return WlrKeyboardFocus.None;
if (root.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
anchors { anchors {
left: true left: true
top: true top: true
right: root.useSingleWindow right: true
bottom: root.useSingleWindow bottom: true
} }
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
WlrLayershell.margins {
left: actualMarginLeft
top: actualMarginTop
right: 0
bottom: 0
}
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: { onVisibleChanged: {
if (visible) if (visible)
return; return;
@@ -528,7 +446,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible enabled: root.closeOnBackgroundClick && root.shouldBeVisible
z: -2 z: -2
onClicked: root.backgroundClicked() onClicked: root.backgroundClicked()
} }
@@ -537,7 +455,7 @@ Item {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
color: "black" color: "black"
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { Behavior on opacity {
@@ -550,17 +468,26 @@ Item {
} }
} }
Item {
id: connectedReveal
// Clip to final footprint while frame-owned chrome grows from the bar edge.
x: root.alignedX
y: root.alignedY
width: root.alignedWidth
height: root.alignedHeight
clip: root.frameOwnsConnectedChrome
Item { Item {
id: modalContainer id: modalContainer
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr) x: Theme.snap(animX, root.dpr)
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr) y: Theme.snap(animY, root.dpr)
width: root.alignedWidth width: root.alignedWidth
height: root.alignedHeight height: root.alignedHeight
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.useSingleWindow && root.shouldBeVisible enabled: root.shouldBeVisible
hoverEnabled: false hoverEnabled: false
acceptedButtons: Qt.AllButtons acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true onPressed: mouse.accepted = true
@@ -579,7 +506,6 @@ 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 connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL) readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
readonly property real offsetX: { readonly property real offsetX: {
@@ -647,7 +573,6 @@ Item {
return directionalTravel; return directionalTravel;
return 0; return 0;
default: default:
// Default to sliding down from top when centered
return -Math.max(directionalTravel, root.screenHeight * 0.24); return -Math.max(directionalTravel, root.screenHeight * 0.24);
} }
} }
@@ -670,7 +595,6 @@ Item {
readonly property real computedScaleCollapsed: root.animationScaleCollapsed readonly property real computedScaleCollapsed: root.animationScaleCollapsed
// openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject { QtObject {
id: morph id: morph
property real openProgress: root.shouldBeVisible ? 1 : 0 property real openProgress: root.shouldBeVisible ? 1 : 0
@@ -801,6 +725,7 @@ Item {
} }
} }
} }
}
FocusScope { FocusScope {
id: focusScope id: focusScope
@@ -205,6 +205,7 @@ Item {
id: clickCatcher id: clickCatcher
visible: false visible: false
color: "transparent" color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher" WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
WlrLayershell.layer: WlrLayershell.Top WlrLayershell.layer: WlrLayershell.Top
@@ -259,15 +260,7 @@ Item {
"error": true "error": true
}) })
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldHaveFocus)
return WlrKeyboardFocus.None;
if (root.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
anchors { anchors {
left: true left: true
@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:color-picker" layerNamespace: "dms:color-picker"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property string pickerTitle: I18n.tr("Choose Color") property string pickerTitle: I18n.tr("Choose Color")
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null property var onColorSelectedCallback: null
@@ -30,7 +30,6 @@ Item {
property string _pendingMode: "" property string _pendingMode: ""
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
// Animation state — matches DankPopout/DankModal pattern
property bool animationsEnabled: true property bool animationsEnabled: true
property bool _motionActive: false property bool _motionActive: false
property real _frozenMotionX: 0 property real _frozenMotionX: 0
@@ -108,8 +107,6 @@ Item {
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side); return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
} }
// frameEdgeInsetForSide is the full inset; do not add frameBarSize.
// Positions the modal flush to the emerge side, centered on the cross axis.
readonly property var _connectedModalPos: { readonly property var _connectedModalPos: {
const fallback = { const fallback = {
"x": (screenWidth - modalWidth) / 2, "x": (screenWidth - modalWidth) / 2,
@@ -175,8 +172,6 @@ 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).
// 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: (!frameOwnsConnectedChrome && 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
@@ -203,29 +198,11 @@ Item {
} }
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
// For directional/depth: window extends from screen top (content slides within) readonly property real _ccX: _connectedChromeX
// For standard: small window tightly around the modal + shadow padding readonly property real _ccY: _connectedChromeY
readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect
// Content window geometry
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr))
readonly property real _cwWidth: alignedWidth + shadowPad * 2
readonly property real _cwHeight: {
if (launcherArcExtenderActive)
return _connectedChromeHeight;
if (Theme.isDirectionalEffect && !Theme.isConnectedEffect)
return screenHeight + shadowPad;
if (Theme.isDepthEffect)
return alignedY + alignedHeight + shadowPad;
return alignedHeight + shadowPad * 2;
}
// Where the content container sits inside the content window
readonly property real _ccX: shadowPad
readonly property real _ccY: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad)
signal dialogClosed signal dialogClosed
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer { Timer {
id: _syncTimer id: _syncTimer
interval: 0 interval: 0
@@ -242,6 +219,7 @@ Item {
id: modalChrome id: modalChrome
modalHandle: root.modalHandle modalHandle: root.modalHandle
claimPrefix: "dms:launcher-v2" claimPrefix: "dms:launcher-v2"
surfaceKind: "launcher"
screenName: root._currentScreenName() screenName: root._currentScreenName()
enabled: root.frameOwnsConnectedChrome enabled: root.frameOwnsConnectedChrome
active: root.spotlightOpen active: root.spotlightOpen
@@ -252,17 +230,38 @@ Item {
} }
function _publishModalChromeState() { function _publishModalChromeState() {
const presented = spotlightOpen || contentWindow.visible;
const phase = !presented ? "hidden" : (isClosing ? "closing" : (!contentWindow.visible ? "opening" : "open"));
const bodyRect = {
"x": _connectedChromeX,
"y": _connectedChromeY,
"width": _connectedChromeWidth,
"height": _connectedChromeHeight
};
const animationOffset = {
"x": contentContainer ? contentContainer.animX : 0,
"y": contentContainer ? contentContainer.animY : 0
};
const state = { const state = {
"visible": spotlightOpen || contentWindow.visible, "kind": "launcher",
"screenName": root._currentScreenName(),
"phase": phase,
"visible": presented,
"presented": presented,
"barSide": resolvedConnectedBarSide, "barSide": resolvedConnectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": _connectedChromeX, "bodyX": _connectedChromeX,
"bodyY": _connectedChromeY, "bodyY": _connectedChromeY,
"bodyW": _connectedChromeWidth, "bodyW": _connectedChromeWidth,
"bodyH": _connectedChromeHeight, "bodyH": _connectedChromeHeight,
"animX": contentContainer ? contentContainer.animX : 0, "animX": animationOffset.x,
"animY": contentContainer ? contentContainer.animY : 0, "animY": animationOffset.y,
"omitStartConnector": false, "omitStartConnector": false,
"omitEndConnector": false "omitEndConnector": false,
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
}; };
return modalChrome.publish(state); return modalChrome.publish(state);
} }
@@ -359,8 +358,6 @@ Item {
return; return;
contentVisible = true; contentVisible = true;
spotlightContent.closeTransientUi?.(); spotlightContent.closeTransientUi?.();
// NOTE: forceActiveFocus() is deliberately NOT called here.
// It is deferred to after animation starts to avoid compositor IPC stalls.
if (spotlightContent.searchField) { if (spotlightContent.searchField) {
spotlightContent.searchField.text = query; spotlightContent.searchField.text = query;
@@ -398,40 +395,29 @@ Item {
isClosing = false; isClosing = false;
openedFromOverview = false; openedFromOverview = false;
// Disable animations so the snap is instant
animationsEnabled = false; animationsEnabled = false;
// Freeze the collapsed offsets (they depend on height which could change)
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0; _frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset); _frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
var focusedScreen = CompositorService.getFocusedScreen(); var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) { if (focusedScreen) {
backgroundWindow.screen = focusedScreen;
contentWindow.screen = focusedScreen; contentWindow.screen = focusedScreen;
} }
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
_motionActive = false; _motionActive = false;
// Make windows visible but do NOT request keyboard focus yet
ModalManager.openModal(modalHandle); ModalManager.openModal(modalHandle);
spotlightOpen = true; spotlightOpen = true;
backgroundWindow.visible = true;
contentWindow.visible = true; contentWindow.visible = true;
if (useHyprlandFocusGrab)
focusGrab.active = true;
// Load content and initialize (but no forceActiveFocus — that's deferred)
_ensureContentLoadedAndInitialize(query || "", mode || ""); _ensureContentLoadedAndInitialize(query || "", mode || "");
// Frame 1: enable animations and trigger enter motion // Defer focus until after enter motion starts (avoids compositor IPC stalls).
Qt.callLater(() => { Qt.callLater(() => {
root.animationsEnabled = true; root.animationsEnabled = true;
root._motionActive = true; root._motionActive = true;
// Frame 2: request keyboard focus + activate search field
// Double-deferred to avoid compositor IPC competing with animation frames
Qt.callLater(() => { Qt.callLater(() => {
root.keyboardActive = true; root.keyboardActive = true;
if (root.spotlightContent && root.spotlightContent.searchField) if (root.spotlightContent && root.spotlightContent.searchField)
@@ -454,16 +440,13 @@ Item {
spotlightContent?.closeTransientUi?.(); spotlightContent?.closeTransientUi?.();
openedFromOverview = false; openedFromOverview = false;
isClosing = true; isClosing = true;
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
if (!Theme.isDirectionalEffect) if (!Theme.isDirectionalEffect)
contentVisible = false; contentVisible = false;
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
_motionActive = false; _motionActive = false;
keyboardActive = false; keyboardActive = false;
spotlightOpen = false; spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle); ModalManager.closeModal(modalHandle);
closeCleanupTimer.start(); closeCleanupTimer.start();
} }
@@ -500,7 +483,6 @@ Item {
isClosing = false; isClosing = false;
contentVisible = false; contentVisible = false;
contentWindow.visible = false; contentWindow.visible = false;
backgroundWindow.visible = false;
if (root.unloadContentOnClose) if (root.unloadContentOnClose)
launcherContentLoader.active = false; launcherContentLoader.active = false;
dialogClosed(); dialogClosed();
@@ -519,7 +501,7 @@ Item {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
windows: [contentWindow] windows: [contentWindow]
active: false active: root.useHyprlandFocusGrab && root.spotlightOpen
onCleared: { onCleared: {
if (spotlightOpen) { if (spotlightOpen) {
@@ -569,7 +551,6 @@ Item {
root._releaseModalChrome(); root._releaseModalChrome();
root._windowEnabled = false; root._windowEnabled = false;
backgroundWindow.screen = newScreen;
contentWindow.screen = newScreen; contentWindow.screen = newScreen;
Qt.callLater(() => { Qt.callLater(() => {
root._windowEnabled = true; root._windowEnabled = true;
@@ -577,73 +558,6 @@ Item {
} }
} }
PanelWindow {
id: backgroundWindow
visible: false
color: "transparent"
readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
WlrLayershell.namespace: "dms:spotlight:bg"
WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.margins {
top: backgroundWindow._topMargin
bottom: backgroundWindow._bottomMargin
left: backgroundWindow._leftMargin
right: backgroundWindow._rightMargin
}
anchors {
top: true
bottom: true
left: true
right: true
}
mask: Region {
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
Region {
item: bgContentHole
intersection: Intersection.Subtract
}
}
Item {
id: bgFullScreenMask
anchors.fill: parent
}
Item {
id: bgContentHole
visible: false
x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin
y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin
width: root.alignedWidth
height: root.contentSurfaceHeight
}
Rectangle {
id: backgroundDarken
anchors.fill: parent
color: "black"
opacity: 0
visible: false
}
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
onClicked: root.hide()
}
}
PanelWindow { PanelWindow {
id: contentWindow id: contentWindow
visible: false visible: false
@@ -663,23 +577,31 @@ Item {
WlrLayershell.namespace: "dms:spotlight" WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors { anchors {
left: true left: true
top: true top: true
right: true
bottom: true
} }
WlrLayershell.margins {
left: root._cwMarginLeft
top: root._cwMarginTop
}
implicitWidth: root._cwWidth
implicitHeight: root._cwHeight
mask: Region { mask: Region {
item: contentInputMask item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask
Region {
item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null
}
}
Item {
id: dismissArea
visible: false
anchors.fill: parent
anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
} }
Item { Item {
@@ -691,16 +613,31 @@ Item {
height: root.contentSurfaceHeight height: root.contentSurfaceHeight
} }
MouseArea {
anchors.fill: dismissArea
enabled: root.spotlightOpen
z: -2
onClicked: root.hide()
}
Item { Item {
id: contentContainer id: contentContainer
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
x: root._ccX x: root._ccX
y: root._ccY y: root._ccY
width: root.alignedWidth width: root.alignedWidth
height: root.contentSurfaceHeight height: root.contentSurfaceHeight
MouseArea {
anchors.fill: parent
enabled: root.spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1 readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
readonly property bool dockTop: dockEdge === 0 readonly property bool dockTop: dockEdge === 0
readonly property bool dockBottom: dockEdge === 1 readonly property bool dockBottom: dockEdge === 1
@@ -755,7 +692,6 @@ Item {
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40); return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
} }
// openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject { QtObject {
id: morph id: morph
property real openProgress: root._motionActive ? 1 : 0 property real openProgress: root._motionActive ? 1 : 0
@@ -814,7 +750,6 @@ Item {
width: contentContainer.width width: contentContainer.width
height: contentContainer.height height: contentContainer.height
// Shadow mirrors contentWrapper position/scale/opacity
ElevationShadow { ElevationShadow {
id: launcherShadowLayer id: launcherShadowLayer
width: parent.width width: parent.width
@@ -832,7 +767,6 @@ Item {
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
} }
// contentWrapper moves inside static contentContainer — DankPopout pattern
Item { Item {
id: contentWrapper id: contentWrapper
width: parent.width width: parent.width
@@ -84,14 +84,14 @@ Item {
readonly property real alignedX: Theme.snap(modalX, dpr) readonly property real alignedX: Theme.snap(modalX, dpr)
readonly property real alignedY: Theme.snap(modalY, dpr) readonly property real alignedY: Theme.snap(modalY, dpr)
// Extra headroom above the window for the slide-in animation // Extra headroom above the content for the slide-in animation
readonly property real _animHeadroom: 16 readonly property real _animHeadroom: 16
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr)) readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr)) readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
readonly property real contentX: Theme.snap(alignedX - windowX, dpr) readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
readonly property real contentY: Theme.snap(alignedY - windowY, dpr) readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
readonly property real windowWidth: alignedWidth + contentX + shadowPad
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr) readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
readonly property real windowWidth: alignedWidth + contentX + shadowPad
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
@@ -114,6 +114,7 @@ Item {
} }
} }
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0 readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
signal dialogClosed signal dialogClosed
@@ -164,8 +165,6 @@ Item {
openedFromOverview = false; openedFromOverview = false;
keyboardActive = true; keyboardActive = true;
ModalManager.openModal(modalHandle); ModalManager.openModal(modalHandle);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize(query || "", mode || ""); _ensureContentLoadedAndInitialize(query || "", mode || "");
} }
@@ -201,7 +200,6 @@ Item {
contentVisible = false; contentVisible = false;
keyboardActive = false; keyboardActive = false;
spotlightOpen = false; spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle); ModalManager.closeModal(modalHandle);
closeCleanupTimer.start(); closeCleanupTimer.start();
} }
@@ -231,7 +229,7 @@ Item {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
windows: [launcherWindow] windows: [launcherWindow]
active: false active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: { onCleared: {
if (spotlightOpen) if (spotlightOpen)
hide(); hide();
@@ -270,8 +268,9 @@ Item {
PanelWindow { PanelWindow {
id: clickCatcher id: clickCatcher
screen: launcherWindow.screen screen: launcherWindow.screen
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken visible: (spotlightOpen || isClosing) && !root.useSingleWindow
color: "transparent" color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: "dms:spotlight:clickcatcher" WlrLayershell.namespace: "dms:spotlight:clickcatcher"
WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.layer: root.effectiveLauncherLayer
@@ -337,24 +336,24 @@ Item {
WlrLayershell.namespace: "dms:spotlight" WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors { anchors {
top: true top: true
left: true left: true
right: root.useBackgroundDarken right: root.useSingleWindow
bottom: root.useBackgroundDarken bottom: root.useSingleWindow
} }
WlrLayershell.margins { WlrLayershell.margins {
left: root.useBackgroundDarken ? 0 : root.windowX left: root.useSingleWindow ? 0 : root.windowX
top: root.useBackgroundDarken ? 0 : root.windowY top: root.useSingleWindow ? 0 : root.windowY
right: 0 right: 0
bottom: 0 bottom: 0
} }
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
mask: Region { mask: Region {
item: inputMask item: inputMask
@@ -364,15 +363,15 @@ Item {
id: inputMask id: inputMask
visible: false visible: false
color: "transparent" color: "transparent"
x: root.useBackgroundDarken ? 0 : modalContainer.x x: root.useSingleWindow ? 0 : modalContainer.x
y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset
width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth
height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.useBackgroundDarken && spotlightOpen enabled: root.useSingleWindow && spotlightOpen
z: -2 z: -2
onClicked: root.hide() onClicked: root.hide()
} }
@@ -396,13 +395,23 @@ Item {
Item { Item {
id: modalContainer id: modalContainer
x: root.useBackgroundDarken ? root.alignedX : root.contentX x: root.useSingleWindow ? root.alignedX : root.contentX
y: root.useBackgroundDarken ? root.alignedY : root.contentY y: root.useSingleWindow ? root.alignedY : root.contentY
width: root.alignedWidth width: root.alignedWidth
height: root._animatedContentH height: root._animatedContentH
visible: _renderActive visible: _renderActive
z: 0 z: 0
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
property bool _renderActive: contentVisible property bool _renderActive: contentVisible
property real slideOffset: contentVisible ? 0 : -root._animHeadroom property real slideOffset: contentVisible ? 0 : -root._animHeadroom
@@ -80,6 +80,7 @@ Item {
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, { readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
"allow": ["top", "overlay"], "allow": ["top", "overlay"],
@@ -172,8 +173,6 @@ Item {
keyboardActive = true; keyboardActive = true;
ModalManager.openModal(modalHandle); ModalManager.openModal(modalHandle);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize(query || "", mode || ""); _ensureContentLoadedAndInitialize(query || "", mode || "");
} }
@@ -211,7 +210,6 @@ Item {
keyboardActive = false; keyboardActive = false;
spotlightOpen = false; spotlightOpen = false;
focusGrab.active = false;
ModalManager.closeModal(modalHandle); ModalManager.closeModal(modalHandle);
closeCleanupTimer.start(); closeCleanupTimer.start();
@@ -262,7 +260,7 @@ Item {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
windows: [launcherWindow] windows: [launcherWindow]
active: false active: root.useHyprlandFocusGrab && root.keyboardActive
onCleared: { onCleared: {
if (spotlightOpen) { if (spotlightOpen) {
@@ -306,8 +304,9 @@ Item {
PanelWindow { PanelWindow {
id: clickCatcher id: clickCatcher
screen: launcherWindow.screen screen: launcherWindow.screen
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken visible: (spotlightOpen || isClosing) && !root.useSingleWindow
color: "transparent" color: "transparent"
updatesEnabled: false
WlrLayershell.namespace: "dms:spotlight:clickcatcher" WlrLayershell.namespace: "dms:spotlight:clickcatcher"
WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.layer: root.effectiveLauncherLayer
@@ -373,24 +372,24 @@ Item {
WlrLayershell.namespace: "dms:spotlight" WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.layer: root.effectiveLauncherLayer
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
anchors { anchors {
top: true top: true
left: true left: true
right: root.useBackgroundDarken right: root.useSingleWindow
bottom: root.useBackgroundDarken bottom: root.useSingleWindow
} }
WlrLayershell.margins { WlrLayershell.margins {
left: root.useBackgroundDarken ? 0 : root.windowX left: root.useSingleWindow ? 0 : root.windowX
top: root.useBackgroundDarken ? 0 : root.windowY top: root.useSingleWindow ? 0 : root.windowY
right: 0 right: 0
bottom: 0 bottom: 0
} }
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
mask: Region { mask: Region {
item: launcherInputMask item: launcherInputMask
@@ -400,15 +399,15 @@ Item {
id: launcherInputMask id: launcherInputMask
visible: false visible: false
color: "transparent" color: "transparent"
x: root.useBackgroundDarken ? 0 : modalContainer.x x: root.useSingleWindow ? 0 : modalContainer.x
y: root.useBackgroundDarken ? 0 : modalContainer.y y: root.useSingleWindow ? 0 : modalContainer.y
width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width width: root.useSingleWindow ? launcherWindow.width : modalContainer.width
height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height height: root.useSingleWindow ? launcherWindow.height : modalContainer.height
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.useBackgroundDarken && spotlightOpen enabled: root.useSingleWindow && spotlightOpen
z: -2 z: -2
onClicked: root.hide() onClicked: root.hide()
} }
@@ -432,13 +431,23 @@ Item {
Item { Item {
id: modalContainer id: modalContainer
x: root.useBackgroundDarken ? root.alignedX : root.contentX x: root.useSingleWindow ? root.alignedX : root.contentX
y: root.useBackgroundDarken ? root.alignedY : root.contentY y: root.useSingleWindow ? root.alignedY : root.contentY
width: root.alignedWidth width: root.alignedWidth
height: root.alignedHeight height: root.alignedHeight
visible: _renderActive visible: _renderActive
z: 0 z: 0
MouseArea {
anchors.fill: parent
enabled: spotlightOpen
hoverEnabled: false
acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true
onClicked: mouse.accepted = true
z: -1
}
property bool _renderActive: contentVisible property bool _renderActive: contentVisible
property real publishedScale: contentVisible ? 1 : 0.96 property real publishedScale: contentVisible ? 1 : 0.96
property real publishedOpacity: contentVisible ? 1 : 0 property real publishedOpacity: contentVisible ? 1 : 0
-6
View File
@@ -1,7 +1,6 @@
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -29,11 +28,6 @@ DankModal {
KeybindsService.loadCheatsheet(); KeybindsService.loadCheatsheet();
} }
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
function scrollDown() { function scrollDown() {
if (!root.activeFlickable) if (!root.activeFlickable)
return; return;
-7
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import qs.Common import qs.Common
@@ -45,12 +44,6 @@ DankModal {
} }
} }
HyprlandFocusGrab {
id: grab
windows: [muxModal.contentWindow]
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
}
function toggle() { function toggle() {
if (shouldBeVisible) { if (shouldBeVisible) {
hide(); hide();
-6
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:notification-center-modal" layerNamespace: "dms:notification-center-modal"
HyprlandFocusGrab {
windows: [notificationModal.contentWindow]
active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus
}
property bool notificationModalOpen: false property bool notificationModalOpen: false
property var notificationListRef: null property var notificationListRef: null
property var historyListRef: null property var historyListRef: null
-6
View File
@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:power-menu" layerNamespace: "dms:power-menu"
keepPopoutsOpen: true keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root.contentWindow]
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
}
property int selectedIndex: 0 property int selectedIndex: 0
property int selectedRow: 0 property int selectedRow: 0
property int selectedCol: 0 property int selectedCol: 0
@@ -109,15 +109,7 @@ DankPopout {
close(); close();
} }
customKeyboardFocus: { customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (anyModalOpen)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
onBackgroundClicked: close() onBackgroundClicked: close()
+1 -2
View File
@@ -61,7 +61,7 @@ Item {
// M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support // M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support
readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0 readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
readonly property var elevLevel: Theme.elevationLevel2 readonly property var elevLevel: Theme.elevationLevel2
readonly property bool shadowEnabled: !BlurService.enabled && ((Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride) readonly property bool shadowEnabled: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride
readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top"))) readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top")))
readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit" readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
@@ -207,7 +207,6 @@ Item {
shadowOffsetX: root.shadowOffsetX shadowOffsetX: root.shadowOffsetX
shadowOffsetY: root.shadowOffsetY shadowOffsetY: root.shadowOffsetY
shadowColor: root.shadowColor shadowColor: root.shadowColor
blurMax: Theme.elevationBlurMax
} }
Loader { Loader {
+27 -15
View File
@@ -9,6 +9,8 @@ PanelWindow {
id: barWindow id: barWindow
readonly property var log: Log.scoped("DankBarWindow") readonly property var log: Log.scoped("DankBarWindow")
Component.onDestruction: KeyboardFocus.unregisterBarWindow(barWindow)
required property var rootWindow required property var rootWindow
required property var barConfig required property var barConfig
property var modelData: item property var modelData: item
@@ -18,6 +20,8 @@ PanelWindow {
property var centerWidgetsModel property var centerWidgetsModel
property var rightWidgetsModel property var rightWidgetsModel
readonly property bool barRevealed: inputMask.showing
property var controlCenterButtonRef: null property var controlCenterButtonRef: null
property var clockButtonRef: null property var clockButtonRef: null
property var systemUpdateButtonRef: null property var systemUpdateButtonRef: null
@@ -282,9 +286,6 @@ PanelWindow {
readonly property bool isVertical: axis.isVertical readonly property bool isVertical: axis.isVertical
property bool gothCornersEnabled: barConfig?.gothCornersEnabled ?? false
property real wingtipsRadius: barConfig?.gothCornerRadiusOverride ? (barConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _surfaceContainer: Theme.surfaceContainer readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property string _barId: barConfig?.id ?? "default" readonly property string _barId: barConfig?.id ?? "default"
property real _backgroundAlpha: barConfig?.transparency ?? 1.0 property real _backgroundAlpha: barConfig?.transparency ?? 1.0
@@ -296,25 +297,30 @@ PanelWindow {
} }
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen) readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
readonly property var renderBarConfig: SettingsData.effectiveBarConfigForRender(barConfig, usesFrameBarChrome)
property bool gothCornersEnabled: renderBarConfig?.gothCornersEnabled ?? false
property real wingtipsRadius: renderBarConfig?.gothCornerRadiusOverride ? (renderBarConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
// Shadow buffer: extra window space for shadow to render beyond bar bounds // Shadow buffer: extra window space for shadow to render beyond bar bounds
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (barConfig?.shadowIntensity ?? 0) > 0 readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (renderBarConfig?.shadowIntensity ?? 0) > 0
readonly property real _shadowBuffer: { readonly property real _shadowBuffer: {
if (!_shadowActive) if (!_shadowActive)
return 0; return 0;
const hasOverride = (barConfig?.shadowIntensity ?? 0) > 0; const hasOverride = (renderBarConfig?.shadowIntensity ?? 0) > 0;
if (hasOverride) { if (hasOverride) {
const blur = (barConfig.shadowIntensity ?? 0) * 0.2; const blur = (renderBarConfig.shadowIntensity ?? 0) * 0.2;
const offset = blur * 0.5; const offset = blur * 0.5;
return Theme.snap(Math.max(16, blur + offset + 8), _dpr); return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
} }
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr); return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
} }
property string screenName: modelData.name
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
// Flatten/spacing collapse for maximized windows is only for frame-integrated layout. // Flatten/spacing collapse for maximized windows is only for frame-integrated layout.
// When the bar draws its own pill, keep rounded corners and spacing like the dock. // When the bar draws its own pill, keep rounded corners and spacing like the dock.
readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesFrameBarChrome readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesFrameBarChrome
@@ -550,11 +556,12 @@ PanelWindow {
} }
screen: modelData screen: modelData
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0 implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0 implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
color: "transparent" color: "transparent"
Component.onCompleted: { Component.onCompleted: {
KeyboardFocus.registerBarWindow(barWindow);
updateGpuTempConfig(); updateGpuTempConfig();
_updateBackgroundAlpha(); _updateBackgroundAlpha();
_updateHasMaximizedToplevel(); _updateHasMaximizedToplevel();
@@ -947,7 +954,7 @@ PanelWindow {
id: barBackground id: barBackground
barWindow: barWindow barWindow: barWindow
axis: axis axis: axis
barConfig: barWindow.barConfig barConfig: barWindow.renderBarConfig
} }
MouseArea { MouseArea {
@@ -956,8 +963,13 @@ PanelWindow {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: { onClicked: {
const screenName = barWindow.screen?.name; const screenName = barWindow.screen?.name;
if (screenName && PopoutManager.currentPopoutsByScreen[screenName]) if (!screenName)
return;
if (PopoutManager.currentPopoutsByScreen[screenName])
PopoutManager.closeAllPopouts(); PopoutManager.closeAllPopouts();
if (ModalManager.currentModalsByScreen[screenName])
ModalManager.closeAllModalsExcept(null);
TrayMenuManager.closeAllMenus();
} }
} }
@@ -38,15 +38,7 @@ DankPopout {
backgroundInteractive: !anyModalOpen backgroundInteractive: !anyModalOpen
customKeyboardFocus: { customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (anyModalOpen)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
Connections { Connections {
target: SystemUpdateService target: SystemUpdateService
@@ -980,21 +980,13 @@ BasePill {
screen: root.parentScreen screen: root.parentScreen
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.menuOpen, null)
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (!root.menuOpen)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
WlrLayershell.namespace: "dms:tray-overflow-menu" WlrLayershell.namespace: "dms:tray-overflow-menu"
color: "transparent" color: "transparent"
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [overflowMenu] windows: [overflowMenu].concat(KeyboardFocus.barWindows)
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen active: root.useOverflowPopup && KeyboardFocus.wantsGrab(root.menuOpen, null)
} }
Connections { Connections {
@@ -1051,32 +1043,21 @@ BasePill {
"leftBar": 0, "leftBar": 0,
"rightBar": 0 "rightBar": 0
}) })
readonly property real effectiveBarSize: root.barThickness + root.barSpacing readonly property real maskX: _overflowDismissZone.x
readonly property real maskY: _overflowDismissZone.y
readonly property real maskWidth: _overflowDismissZone.width
readonly property real maskHeight: _overflowDismissZone.height
readonly property real maskX: { DismissZone {
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; id: _overflowDismissZone
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; barPosition: overflowMenu.barPosition
return Math.max(triggeringBarX, adjacentLeftBar); barX: overflowMenu.barX
} barY: overflowMenu.barY
barWidth: overflowMenu.barWidth
readonly property real maskY: { barHeight: overflowMenu.barHeight
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; screenWidth: overflowMenu.width
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; screenHeight: overflowMenu.height
return Math.max(triggeringBarY, adjacentTopBar); adjacentBarInfo: overflowMenu.adjacentBarInfo
}
readonly property real maskWidth: {
const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
return Math.max(100, width - maskX - rightExclusion);
}
readonly property real maskHeight: {
const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
return Math.max(100, height - maskY - bottomExclusion);
} }
mask: Region { mask: Region {
@@ -1237,13 +1218,7 @@ BasePill {
fallbackOffset: 6 fallbackOffset: 6
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
sourceRect.smooth: true
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled
layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
layer.samples: 4
} }
Rectangle { Rectangle {
@@ -1450,20 +1425,12 @@ BasePill {
screen: menuRoot.parentScreen screen: menuRoot.parentScreen
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(menuRoot.showMenu, null)
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (!menuRoot.showMenu)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
color: "transparent" color: "transparent"
HyprlandFocusGrab { HyprlandFocusGrab {
windows: [menuWindow] windows: [menuWindow].concat(KeyboardFocus.barWindows)
active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu active: KeyboardFocus.wantsGrab(menuRoot.showMenu, null)
} }
anchors { anchors {
@@ -1502,32 +1469,21 @@ BasePill {
"leftBar": 0, "leftBar": 0,
"rightBar": 0 "rightBar": 0
}) })
readonly property real effectiveBarSize: root.barThickness + root.barSpacing readonly property real maskX: _menuDismissZone.x
readonly property real maskY: _menuDismissZone.y
readonly property real maskWidth: _menuDismissZone.width
readonly property real maskHeight: _menuDismissZone.height
readonly property real maskX: { DismissZone {
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; id: _menuDismissZone
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; barPosition: menuWindow.barPosition
return Math.max(triggeringBarX, adjacentLeftBar); barX: menuWindow.barX
} barY: menuWindow.barY
barWidth: menuWindow.barWidth
readonly property real maskY: { barHeight: menuWindow.barHeight
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; screenWidth: menuWindow.width
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; screenHeight: menuWindow.height
return Math.max(triggeringBarY, adjacentTopBar); adjacentBarInfo: menuWindow.adjacentBarInfo
}
readonly property real maskWidth: {
const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0;
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
return Math.max(100, width - maskX - rightExclusion);
}
readonly property real maskHeight: {
const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0;
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
return Math.max(100, height - maskY - bottomExclusion);
} }
mask: Region { mask: Region {
@@ -1689,11 +1645,7 @@ BasePill {
fallbackOffset: 6 fallbackOffset: 6
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
sourceRect.antialiasing: true shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled
layer.smooth: true
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
} }
Rectangle { Rectangle {
@@ -130,7 +130,7 @@ Item {
borderColor: volumePanel.border.color borderColor: volumePanel.border.color
borderWidth: volumePanel.border.width borderWidth: volumePanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled shadowEnabled: Theme.elevationEnabled
} }
MouseArea { MouseArea {
@@ -272,7 +272,7 @@ Item {
borderColor: audioDevicesPanel.border.color borderColor: audioDevicesPanel.border.color
borderWidth: audioDevicesPanel.border.width borderWidth: audioDevicesPanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled shadowEnabled: Theme.elevationEnabled
} }
MouseArea { MouseArea {
@@ -444,7 +444,7 @@ Item {
borderColor: playersPanel.border.color borderColor: playersPanel.border.color
borderWidth: playersPanel.border.width borderWidth: playersPanel.border.width
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled shadowEnabled: Theme.elevationEnabled
} }
MouseArea { MouseArea {
+57 -14
View File
@@ -186,13 +186,36 @@ Variants {
return; return;
} }
const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps;
const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open"));
const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x;
const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y;
const bodyW = dock.hasApps ? dockBackground.width : 0;
const bodyH = dock.hasApps ? dockBackground.height : 0;
ConnectedModeState.setDockState(dock._dockScreenName, { ConnectedModeState.setDockState(dock._dockScreenName, {
"reveal": dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps, "kind": "dock",
"screenName": dock._dockScreenName,
"phase": phase,
"visible": presented,
"presented": presented,
"reveal": presented,
"barSide": dock.connectedBarSide, "barSide": dock.connectedBarSide,
"bodyX": dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x, "bodyRect": {
"bodyY": dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y, "x": bodyX,
"bodyW": dock.hasApps ? dockBackground.width : 0, "y": bodyY,
"bodyH": dock.hasApps ? dockBackground.height : 0, "width": bodyW,
"height": bodyH
},
"animationOffset": {
"x": dockSlide.x,
"y": dockSlide.y
},
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": bodyX,
"bodyY": bodyY,
"bodyW": bodyW,
"bodyH": bodyH,
"slideX": dockSlide.x, "slideX": dockSlide.x,
"slideY": dockSlide.y "slideY": dockSlide.y
}); });
@@ -724,16 +747,36 @@ Variants {
onHeightChanged: dock._syncDockChromeState() onHeightChanged: dock._syncDockChromeState()
} }
ConnectedShape { Item {
id: dockConnectedChrome
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
barSide: dock.connectedBarSide readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
bodyWidth: dockBackground.width readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
bodyHeight: dockBackground.height readonly property real bodyRadius: dock.surfaceRadius
connectorRadius: Theme.connectedCornerRadius readonly property bool barTop: dock.connectedBarSide === "top"
surfaceRadius: dock.surfaceRadius readonly property bool barBottom: dock.connectedBarSide === "bottom"
fillColor: dock.surfaceColor readonly property bool barLeft: dock.connectedBarSide === "left"
x: dockBackground.x - bodyX readonly property bool barRight: dock.connectedBarSide === "right"
y: dockBackground.y - bodyY
x: dockBackground.x - extraLeft
y: dockBackground.y - extraTop
width: dockBackground.width + extraLeft * 2
height: dockBackground.height + extraTop * 2
ShaderEffect {
anchors.fill: parent
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
property real widthPx: width
property real heightPx: height
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
}
} }
Shape { Shape {
+8 -33
View File
@@ -1,9 +1,9 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import qs.Common import qs.Common
// Frame perimeter ring with rounded cutout (SDF).
Item { Item {
id: root id: root
@@ -16,39 +16,14 @@ Item {
required property real cutoutRadius required property real cutoutRadius
property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
Rectangle { ShaderEffect {
id: borderRect
anchors.fill: parent anchors.fill: parent
// Bake frameOpacity into the color alpha rather than using the `opacity` property fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/frame_arc.frag.qsb")
color: root.borderColor
layer.enabled: true property real widthPx: width
layer.effect: MultiEffect { property real heightPx: height
maskSource: cutoutMask property real cutoutRadius: root.cutoutRadius
maskEnabled: true property vector4d cutout: Qt.vector4d(root.cutoutLeftInset, root.cutoutTopInset, root.width - root.cutoutRightInset, root.height - root.cutoutBottomInset)
maskInverted: true property vector4d surfaceColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a)
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
Item {
id: cutoutMask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors {
fill: parent
topMargin: root.cutoutTopInset
bottomMargin: root.cutoutBottomInset
leftMargin: root.cutoutLeftInset
rightMargin: root.cutoutRightInset
}
radius: root.cutoutRadius
}
} }
} }
File diff suppressed because it is too large Load Diff
@@ -31,7 +31,7 @@ Rectangle {
height: baseCardHeight + contentItem.extraHeight height: baseCardHeight + contentItem.extraHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
clip: false clip: false
readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
ElevationShadow { ElevationShadow {
id: shadowLayer id: shadowLayer
@@ -47,7 +47,7 @@ Rectangle {
readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
radius: connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius radius: connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence
readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
readonly property var shadowElevation: Theme.elevationLevel1 readonly property var shadowElevation: Theme.elevationLevel1
readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4 readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4
readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0 readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0
@@ -641,21 +641,15 @@ PanelWindow {
shadowOffsetY: content.shadowOffsetY shadowOffsetY: content.shadowOffsetY
shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent" shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
sourceRect.anchors.fill: undefined sourceX: content.shadowRenderPadding + content.cardInset
sourceRect.x: content.shadowRenderPadding + content.cardInset sourceY: content.shadowRenderPadding + content.cardInset
sourceRect.y: content.shadowRenderPadding + content.cardInset sourceWidth: Math.max(0, content.width - (content.cardInset * 2))
sourceRect.width: Math.max(0, content.width - (content.cardInset * 2)) sourceHeight: Math.max(0, content.height - (content.cardInset * 2))
sourceRect.height: Math.max(0, content.height - (content.cardInset * 2)) targetRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius targetColor: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface
sourceRect.color: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface borderColor: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
sourceRect.antialiasing: true borderWidth: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
sourceRect.layer.enabled: false
sourceRect.layer.textureSize: Qt.size(0, 0)
sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
} }
// Keep critical accent outside shadow rendering so connected mode still shows it. // Keep critical accent outside shadow rendering so connected mode still shows it.
@@ -513,13 +513,30 @@ QtObject {
ConnectedModeState.clearNotificationState(screenName); ConnectedModeState.clearNotificationState(screenName);
return; return;
} }
const bodyRect = {
x: minX,
y: minY,
width: maxXEnd - minX,
height: maxYEnd - minY
};
ConnectedModeState.setNotificationState(screenName, { ConnectedModeState.setNotificationState(screenName, {
kind: "notification",
screenName: screenName,
phase: "open",
visible: true, visible: true,
presented: true,
barSide: notifBarSide, barSide: notifBarSide,
bodyRect: bodyRect,
animationOffset: {
x: 0,
y: 0
},
scale: 1,
opacity: Theme.connectedSurfaceColor.a,
bodyX: minX, bodyX: minX,
bodyY: minY, bodyY: minY,
bodyW: maxXEnd - minX, bodyW: bodyRect.width,
bodyH: maxYEnd - minY, bodyH: bodyRect.height,
omitStartConnector: _notificationOmitStartConnector(), omitStartConnector: _notificationOmitStartConnector(),
omitEndConnector: _notificationOmitEndConnector() omitEndConnector: _notificationOmitEndConnector()
}); });
@@ -23,6 +23,7 @@ Item {
property bool conditionVisible: true property bool conditionVisible: true
property bool _visibilityOverride: false property bool _visibilityOverride: false
property bool _visibilityOverrideValue: true property bool _visibilityOverrideValue: true
readonly property bool _barRevealed: blurBarWindow?.barRevealed ?? true
readonly property bool effectiveVisible: { readonly property bool effectiveVisible: {
if (_visibilityOverride) if (_visibilityOverride)
@@ -122,6 +123,11 @@ Item {
conditionVisible = true; conditionVisible = true;
} }
on_BarRevealedChanged: {
if (_barRevealed && visibilityCommand && !_visibilityOverride)
checkVisibility();
}
onVisibilityIntervalChanged: { onVisibilityIntervalChanged: {
if (visibilityInterval > 0 && visibilityCommand) { if (visibilityInterval > 0 && visibilityCommand) {
visibilityTimer.restart(); visibilityTimer.restart();
@@ -134,7 +140,7 @@ Item {
id: visibilityTimer id: visibilityTimer
interval: root.visibilityInterval * 1000 interval: root.visibilityInterval * 1000
repeat: true repeat: true
running: root.visibilityInterval > 0 && root.visibilityCommand !== "" running: root.visibilityInterval > 0 && root.visibilityCommand !== "" && root._barRevealed && !root._visibilityOverride
onTriggered: root.checkVisibility() onTriggered: root.checkVisibility()
} }
@@ -152,6 +152,9 @@ Item {
} }
] ]
readonly property var entryActionKeys: ["pin", "edit", "delete"]
readonly property var entryActionLabels: [I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
function getMaxHistoryText(value) { function getMaxHistoryText(value) {
if (value <= 0) if (value <= 0)
return "∞"; return "∞";
@@ -187,6 +190,29 @@ Item {
return value.toString(); return value.toString();
} }
function visibleEntryActionKeys() {
return SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"];
}
function visibleEntryActionLabels() {
const visibleKeys = visibleEntryActionKeys();
return entryActionKeys.map((key, index) => visibleKeys.includes(key) ? entryActionLabels[index] : null).filter(label => label !== null);
}
function setVisibleEntryAction(index, selected) {
const actionKey = entryActionKeys[index];
if (!actionKey)
return;
let actions = visibleEntryActionKeys().slice();
if (selected && !actions.includes(actionKey)) {
actions.push(actionKey);
} else if (!selected && actions.includes(actionKey)) {
actions = actions.filter(action => action !== actionKey);
}
SettingsData.set("clipboardVisibleEntryActions", actions);
}
function loadConfig() { function loadConfig() {
configLoaded = false; configLoaded = false;
configError = false; configError = false;
@@ -437,6 +463,24 @@ Item {
checked: SettingsData.clipboardEnterToPaste checked: SettingsData.clipboardEnterToPaste
onToggled: checked => SettingsData.set("clipboardEnterToPaste", checked) onToggled: checked => SettingsData.set("clipboardEnterToPaste", checked)
} }
SettingsButtonGroupRow {
tab: "clipboard"
tags: ["clipboard", "actions", "buttons", "hide", "density", "pin", "edit", "delete"]
settingKey: "clipboardVisibleEntryActions"
text: I18n.tr("Visible Entry Actions")
description: I18n.tr("Choose which action buttons appear on clipboard entries")
selectionMode: "multi"
model: root.entryActionLabels
currentSelection: root.visibleEntryActionLabels()
checkEnabled: false
buttonHeight: 28
minButtonWidth: 56
buttonPadding: Theme.spacingS
textSize: Theme.fontSizeSmall
spacing: 1
onSelectionChanged: (index, selected) => root.setVisibleEntryAction(index, selected)
}
} }
SettingsCard { SettingsCard {
@@ -64,6 +64,8 @@ Item {
property alias model: buttonGroup.model property alias model: buttonGroup.model
property alias currentIndex: buttonGroup.currentIndex property alias currentIndex: buttonGroup.currentIndex
property alias initialSelection: buttonGroup.initialSelection
property alias currentSelection: buttonGroup.currentSelection
property alias selectionMode: buttonGroup.selectionMode property alias selectionMode: buttonGroup.selectionMode
property alias buttonHeight: buttonGroup.buttonHeight property alias buttonHeight: buttonGroup.buttonHeight
property alias minButtonWidth: buttonGroup.minButtonWidth property alias minButtonWidth: buttonGroup.minButtonWidth
+110
View File
@@ -0,0 +1,110 @@
#version 450
// Connected frame silhouette: frame ring + chrome bodies as one SDF with elevation shadow.
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float widthPx;
float heightPx;
float cutoutRadius;
vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom
vec4 surfaceColor; // straight (non-premultiplied) rgba
vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms
vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px
vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha
// Up to four chrome slots. rect = x,y,w,h (px). corner = per-corner radii,
// k = per-corner junction fillet radii (both topLeft, topRight, bottomRight,
// bottomLeft; a corner is sharp exactly where its k > 0). param = active, 0, 0, 0
vec4 chromeRect0;
vec4 chromeCorner0;
vec4 chromeK0;
vec4 chromeParam0;
vec4 chromeRect1;
vec4 chromeCorner1;
vec4 chromeK1;
vec4 chromeParam1;
vec4 chromeRect2;
vec4 chromeCorner2;
vec4 chromeK2;
vec4 chromeParam2;
vec4 chromeRect3;
vec4 chromeCorner3;
vec4 chromeK3;
vec4 chromeParam3;
} ubuf;
float sdBox(vec2 p, vec2 c, vec2 hs) {
vec2 q = abs(p - c) - hs;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0)));
}
float sdRoundBox(vec2 p, vec2 c, vec2 hs, float r) {
r = min(r, min(hs.x, hs.y));
vec2 q = abs(p - c) - hs + r;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r;
}
float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) {
p -= c;
float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x);
rr = min(rr, min(hs.x, hs.y));
vec2 q = abs(p) - hs + rr;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr;
}
float smin(float a, float b, float k) {
if (k <= 0.0)
return min(a, b);
return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0)));
}
float chromeDist(vec2 px, vec4 rect, vec4 corner) {
vec2 c = rect.xy + rect.zw * 0.5;
return sdRoundBox4(px, c, rect.zw * 0.5, corner);
}
float chromeK(vec2 px, vec4 rect, vec4 ks) {
vec2 p = px - (rect.xy + rect.zw * 0.5);
return (p.x >= 0.0) ? (p.y >= 0.0 ? ks.z : ks.y) : (p.y >= 0.0 ? ks.w : ks.x);
}
float sceneDist(vec2 px) {
vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5;
float dOuter = sdBox(px, sc, sc);
vec2 cutC = vec2((ubuf.cutout.x + ubuf.cutout.z) * 0.5, (ubuf.cutout.y + ubuf.cutout.w) * 0.5);
vec2 cutH = vec2((ubuf.cutout.z - ubuf.cutout.x) * 0.5, (ubuf.cutout.w - ubuf.cutout.y) * 0.5);
float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius);
float d = max(dOuter, -dCut);
if (ubuf.chromeParam0.x > 0.5)
d = smin(d, chromeDist(px, ubuf.chromeRect0, ubuf.chromeCorner0), chromeK(px, ubuf.chromeRect0, ubuf.chromeK0));
if (ubuf.chromeParam1.x > 0.5)
d = smin(d, chromeDist(px, ubuf.chromeRect1, ubuf.chromeCorner1), chromeK(px, ubuf.chromeRect1, ubuf.chromeK1));
if (ubuf.chromeParam2.x > 0.5)
d = smin(d, chromeDist(px, ubuf.chromeRect2, ubuf.chromeCorner2), chromeK(px, ubuf.chromeRect2, ubuf.chromeK2));
if (ubuf.chromeParam3.x > 0.5)
d = smin(d, chromeDist(px, ubuf.chromeRect3, ubuf.chromeCorner3), chromeK(px, ubuf.chromeRect3, ubuf.chromeK3));
return d;
}
void main() {
vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx);
float d = sceneDist(px);
float fw = max(fwidth(d), 1e-4);
float cov = 1.0 - smoothstep(-fw, fw, d);
vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov;
if (ubuf.shadowColor.a > 0.0) {
float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y;
float bk = max(ubuf.shadowParam.x, fw);
float covK = 1.0 - smoothstep(-bk, bk, dk);
float ba = max(ubuf.ambientParam.x, fw);
float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y);
float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z);
col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a));
}
fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity);
}
@@ -0,0 +1,63 @@
#version 450
// Popout-local connected chrome body + bar-edge connector as one SDF.
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float widthPx;
float heightPx;
vec4 surfaceColor; // straight (non-premultiplied) rgba
vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms
vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px
vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha
vec4 bodyRect; // body rounded rect in item px: x, y, w, h
vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft
vec4 edgeParam; // x = bar side (0 top, 1 bottom, 2 left, 3 right), y = fillet k
} ubuf;
float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) {
p -= c;
float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x);
rr = min(rr, min(hs.x, hs.y));
vec2 q = abs(p) - hs + rr;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr;
}
float smin(float a, float b, float k) {
if (k <= 0.0)
return min(a, b);
return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0)));
}
float sceneDist(vec2 px) {
float side = ubuf.edgeParam.x;
float dEdge = side < 0.5 ? px.y
: side < 1.5 ? (ubuf.heightPx - px.y)
: side < 2.5 ? px.x
: (ubuf.widthPx - px.x);
vec2 hs = ubuf.bodyRect.zw * 0.5;
float dBody = sdRoundBox4(px, ubuf.bodyRect.xy + hs, hs, ubuf.cornerRadius);
return smin(dEdge, dBody, ubuf.edgeParam.y);
}
void main() {
vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx);
float d = sceneDist(px);
float fw = max(fwidth(d), 1e-4);
float cov = 1.0 - smoothstep(-fw, fw, d);
vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov;
if (ubuf.shadowColor.a > 0.0) {
float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y;
float bk = max(ubuf.shadowParam.x, fw);
float covK = 1.0 - smoothstep(-bk, bk, dk);
float ba = max(ubuf.ambientParam.x, fw);
float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y);
float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z);
col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a));
}
fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity);
}
@@ -0,0 +1,54 @@
#version 450
// Standalone rounded rect with border and M3 elevation shadow as one SDF.
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float widthPx;
float heightPx;
float borderWidth;
vec4 rectPx; // rounded rect in item px: x, y, w, h
vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft
vec4 fillColor; // straight (non-premultiplied) rgba
vec4 borderColor; // straight rgba
vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms
vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px
vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha
} ubuf;
float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) {
p -= c;
float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x);
rr = min(rr, min(hs.x, hs.y));
vec2 q = abs(p) - hs + rr;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr;
}
float rectDist(vec2 px) {
vec2 hs = ubuf.rectPx.zw * 0.5;
return sdRoundBox4(px, ubuf.rectPx.xy + hs, hs, ubuf.cornerRadius);
}
void main() {
vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx);
float d = rectDist(px);
float fw = max(fwidth(d), 1e-4);
float cov = 1.0 - smoothstep(-fw, fw, d);
float covInner = 1.0 - smoothstep(-fw, fw, d + ubuf.borderWidth);
vec4 col = vec4(ubuf.fillColor.rgb, 1.0) * (ubuf.fillColor.a * covInner)
+ vec4(ubuf.borderColor.rgb, 1.0) * (ubuf.borderColor.a * max(0.0, cov - covInner));
if (ubuf.shadowColor.a > 0.0) {
float dk = rectDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y;
float bk = max(ubuf.shadowParam.x, fw);
float covK = 1.0 - smoothstep(-bk, bk, dk);
float ba = max(ubuf.ambientParam.x, fw);
float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y);
float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z);
col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - cov));
}
fragColor = col * ubuf.qt_Opacity;
}
+42
View File
@@ -0,0 +1,42 @@
#version 450
// Frame perimeter ring with rounded cutout as one SDF.
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float widthPx;
float heightPx;
float cutoutRadius;
vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom
vec4 surfaceColor; // straight (non-premultiplied) rgba
} ubuf;
float sdBox(vec2 p, vec2 c, vec2 hs) {
vec2 q = abs(p - c) - hs;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0)));
}
float sdRoundBox(vec2 p, vec2 c, vec2 hs, float r) {
r = min(r, min(hs.x, hs.y));
vec2 q = abs(p - c) - hs + r;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r;
}
void main() {
vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx);
vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5;
float dOuter = sdBox(px, sc, sc);
vec2 cutC = vec2((ubuf.cutout.x + ubuf.cutout.z) * 0.5, (ubuf.cutout.y + ubuf.cutout.w) * 0.5);
vec2 cutH = vec2((ubuf.cutout.z - ubuf.cutout.x) * 0.5, (ubuf.cutout.w - ubuf.cutout.y) * 0.5);
float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius);
float d = max(dOuter, -dCut);
float fw = max(fwidth(d), 1e-4);
float cov = 1.0 - smoothstep(-fw, fw, d);
float a = ubuf.surfaceColor.a * cov * ubuf.qt_Opacity;
fragColor = vec4(ubuf.surfaceColor.rgb * a, a);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-151
View File
@@ -1,151 +0,0 @@
import QtQuick
import QtQuick.Shapes
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
// Concave arc connector filling the gap between a bar corner and an adjacent surface.
//
// NOTE: FrameWindow now uses ConnectedShape.qml for frame-owned connected chrome
// (unified single-path rendering). This component is still used by DankPopout's
// own shadow source for non-frame-owned chrome (popouts on non-frame screens).
Item {
id: root
property string barSide: "top"
property string placement: "left"
property real spacing: 4
property real connectorRadius: 12
property color color: "transparent"
property real edgeStrokeWidth: 0
property color edgeStrokeColor: color
property real dpr: 1
readonly property bool isHorizontalBar: barSide === "top" || barSide === "bottom"
readonly property bool isPlacementLeft: placement === "left"
readonly property real _edgeStrokeWidth: Math.max(0, edgeStrokeWidth)
readonly property string arcCorner: ConnectorGeometry.arcCorner(barSide, placement)
readonly property real pathStartX: {
switch (arcCorner) {
case "topLeft":
return width;
case "topRight":
case "bottomLeft":
return 0;
default:
return 0;
}
}
readonly property real pathStartY: {
switch (arcCorner) {
case "bottomRight":
return height;
default:
return 0;
}
}
readonly property real firstLineX: {
switch (arcCorner) {
case "topLeft":
case "bottomLeft":
return width;
default:
return 0;
}
}
readonly property real firstLineY: {
switch (arcCorner) {
case "topLeft":
case "topRight":
return height;
default:
return 0;
}
}
readonly property real secondLineX: {
switch (arcCorner) {
case "topRight":
case "bottomLeft":
case "bottomRight":
return width;
default:
return 0;
}
}
readonly property real secondLineY: {
switch (arcCorner) {
case "topLeft":
case "topRight":
case "bottomLeft":
return height;
default:
return 0;
}
}
readonly property real arcCenterX: arcCorner === "topRight" || arcCorner === "bottomRight" ? width : 0
readonly property real arcCenterY: arcCorner === "bottomLeft" || arcCorner === "bottomRight" ? height : 0
readonly property real arcStartAngle: {
switch (arcCorner) {
case "topLeft":
case "topRight":
return 90;
case "bottomLeft":
return 0;
default:
return -90;
}
}
readonly property real arcSweepAngle: {
switch (arcCorner) {
case "topRight":
return 90;
default:
return -90;
}
}
width: isHorizontalBar ? connectorRadius : (spacing + connectorRadius)
height: isHorizontalBar ? (spacing + connectorRadius) : connectorRadius
Shape {
x: -root._edgeStrokeWidth
y: -root._edgeStrokeWidth
width: root.width + root._edgeStrokeWidth * 2
height: root.height + root._edgeStrokeWidth * 2
asynchronous: false
antialiasing: true
preferredRendererType: Shape.CurveRenderer
layer.enabled: true
layer.smooth: true
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
ShapePath {
fillColor: root.color
strokeColor: root._edgeStrokeWidth > 0 ? root.edgeStrokeColor : "transparent"
strokeWidth: root._edgeStrokeWidth * 2
joinStyle: ShapePath.RoundJoin
capStyle: ShapePath.RoundCap
fillRule: ShapePath.WindingFill
startX: root.pathStartX + root._edgeStrokeWidth
startY: root.pathStartY + root._edgeStrokeWidth
PathLine {
x: root.firstLineX + root._edgeStrokeWidth
y: root.firstLineY + root._edgeStrokeWidth
}
PathLine {
x: root.secondLineX + root._edgeStrokeWidth
y: root.secondLineY + root._edgeStrokeWidth
}
PathAngleArc {
centerX: root.arcCenterX + root._edgeStrokeWidth
centerY: root.arcCenterY + root._edgeStrokeWidth
radiusX: root.connectorRadius
radiusY: root.connectorRadius
startAngle: root.arcStartAngle
sweepAngle: root.arcSweepAngle
}
}
}
}
-414
View File
@@ -1,414 +0,0 @@
import QtQuick
import QtQuick.Shapes
import qs.Common
// Unified connected silhouette: body + near/far concave arcs as one ShapePath.
// Keeping the connected chrome in one path avoids sibling alignment seams.
Item {
id: root
property string barSide: "top"
property real bodyWidth: 0
property real bodyHeight: 0
property real connectorRadius: 12
property real startConnectorRadius: connectorRadius
property real endConnectorRadius: connectorRadius
property real farStartConnectorRadius: 0
property real farEndConnectorRadius: 0
property real surfaceRadius: 12
property color fillColor: "transparent"
readonly property bool _horiz: barSide === "top" || barSide === "bottom"
readonly property real _sc: Math.max(0, startConnectorRadius)
readonly property real _ec: Math.max(0, endConnectorRadius)
readonly property real _fsc: Math.max(0, farStartConnectorRadius)
readonly property real _fec: Math.max(0, farEndConnectorRadius)
readonly property real _firstCr: barSide === "left" ? _sc : _ec
readonly property real _secondCr: barSide === "left" ? _ec : _sc
readonly property real _firstFarCr: barSide === "left" ? _fsc : _fec
readonly property real _secondFarCr: barSide === "left" ? _fec : _fsc
readonly property real _farExtent: Math.max(_fsc, _fec)
readonly property real _sr: Math.max(0, Math.min(surfaceRadius, (_horiz ? bodyWidth : bodyHeight) / 2, (_horiz ? bodyHeight : bodyWidth) / 2))
readonly property real _firstSr: _firstFarCr > 0 ? 0 : _sr
readonly property real _secondSr: _secondFarCr > 0 ? 0 : _sr
readonly property real _firstFarInset: _firstFarCr > 0 ? _firstFarCr : _firstSr
readonly property real _secondFarInset: _secondFarCr > 0 ? _secondFarCr : _secondSr
// Root-level aliases PathArc/PathLine elements can't use `parent`.
readonly property real _bw: bodyWidth
readonly property real _bh: bodyHeight
readonly property real _bodyLeft: _horiz ? _sc : (barSide === "right" ? _farExtent : 0)
readonly property real _bodyRight: _bodyLeft + _bw
readonly property real _bodyTop: _horiz ? (barSide === "bottom" ? _farExtent : 0) : _sc
readonly property real _bodyBottom: _bodyTop + _bh
readonly property real _totalW: _horiz ? _bw + _sc + _ec : _bw + _farExtent
readonly property real _totalH: _horiz ? _bh + _farExtent : _bh + _sc + _ec
width: _totalW
height: _totalH
readonly property real bodyX: root._bodyLeft
readonly property real bodyY: root._bodyTop
Shape {
anchors.fill: parent
asynchronous: false
preferredRendererType: Shape.CurveRenderer
antialiasing: true
ShapePath {
fillColor: root.fillColor
strokeWidth: -1
fillRule: ShapePath.WindingFill
// CW path: bar edge concave arc body convex arc far edge convex arc body concave arc
startX: root.barSide === "right" ? root._totalW : 0
startY: {
switch (root.barSide) {
case "bottom":
return root._totalH;
case "left":
return root._totalH;
case "right":
return 0;
default:
return 0;
}
}
// Bar edge
PathLine {
x: {
switch (root.barSide) {
case "left":
return 0;
case "right":
return root._totalW;
default:
return root._totalW;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._totalH;
case "left":
return 0;
case "right":
return root._totalH;
default:
return 0;
}
}
}
// Concave arc 1
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._firstCr;
case "right":
return -root._firstCr;
default:
return -root._firstCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._firstCr;
case "left":
return root._firstCr;
case "right":
return -root._firstCr;
default:
return root._firstCr;
}
}
radiusX: root._firstCr
radiusY: root._firstCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
// Body edge to first convex corner
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyRight - root._firstSr;
case "right":
return root._bodyLeft + root._firstSr;
default:
return root._bodyRight;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyTop + root._firstSr;
case "left":
return root._bodyTop;
case "right":
return root._bodyBottom;
default:
return root._bodyBottom - root._firstSr;
}
}
}
// Convex arc 1
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._firstSr;
case "right":
return -root._firstSr;
default:
return -root._firstSr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._firstSr;
case "left":
return root._firstSr;
case "right":
return -root._firstSr;
default:
return root._firstSr;
}
}
radiusX: root._firstSr
radiusY: root._firstSr
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
}
// Opposite-side connector 1
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._firstFarCr > 0 ? root._bodyRight + root._firstFarCr : root._bodyRight;
case "right":
return root._firstFarCr > 0 ? root._bodyLeft - root._firstFarCr : root._bodyLeft;
default:
return root._firstFarCr > 0 ? root._bodyRight : root._bodyRight - root._firstSr;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._firstFarCr > 0 ? root._bodyTop - root._firstFarCr : root._bodyTop;
case "left":
return root._firstFarCr > 0 ? root._bodyTop : root._bodyTop + root._firstSr;
case "right":
return root._firstFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._firstSr;
default:
return root._firstFarCr > 0 ? root._bodyBottom + root._firstFarCr : root._bodyBottom;
}
}
}
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._firstFarCr;
case "right":
return root._firstFarCr;
default:
return -root._firstFarCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._firstFarCr;
case "left":
return root._firstFarCr;
case "right":
return -root._firstFarCr;
default:
return -root._firstFarCr;
}
}
radiusX: root._firstFarCr
radiusY: root._firstFarCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
// Far edge
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyRight;
case "right":
return root._bodyLeft;
default:
return root._bodyLeft + root._secondFarInset;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyTop;
case "left":
return root._bodyBottom - root._secondFarInset;
case "right":
return root._bodyTop + root._secondFarInset;
default:
return root._bodyBottom;
}
}
}
// Opposite-side connector 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return root._secondFarCr;
case "right":
return -root._secondFarCr;
default:
return -root._secondFarCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return -root._secondFarCr;
case "left":
return root._secondFarCr;
case "right":
return -root._secondFarCr;
default:
return root._secondFarCr;
}
}
radiusX: root._secondFarCr
radiusY: root._secondFarCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._secondFarCr > 0 ? root._bodyRight : root._bodyRight;
case "right":
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft;
default:
return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft + root._secondSr;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop;
case "left":
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._secondSr;
case "right":
return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop + root._secondSr;
default:
return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom;
}
}
}
// Convex arc 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._secondSr;
case "right":
return root._secondSr;
default:
return -root._secondSr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._secondSr;
case "left":
return root._secondSr;
case "right":
return -root._secondSr;
default:
return -root._secondSr;
}
}
radiusX: root._secondSr
radiusY: root._secondSr
direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise
}
// Body edge to second concave arc
PathLine {
x: {
switch (root.barSide) {
case "left":
return root._bodyLeft + root._ec;
case "right":
return root._bodyRight - root._sc;
default:
return root._bodyLeft;
}
}
y: {
switch (root.barSide) {
case "bottom":
return root._bodyBottom - root._sc;
case "left":
return root._bodyBottom;
case "right":
return root._bodyTop;
default:
return root._bodyTop + root._sc;
}
}
}
// Concave arc 2
PathArc {
relativeX: {
switch (root.barSide) {
case "left":
return -root._secondCr;
case "right":
return root._secondCr;
default:
return -root._secondCr;
}
}
relativeY: {
switch (root.barSide) {
case "bottom":
return root._secondCr;
case "left":
return root._secondCr;
case "right":
return -root._secondCr;
default:
return -root._secondCr;
}
}
radiusX: root._secondCr
radiusY: root._secondCr
direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise
}
}
}
}
-2
View File
@@ -289,8 +289,6 @@ PanelWindow {
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
} }
MouseArea { MouseArea {
+15 -2
View File
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -50,6 +51,20 @@ Item {
readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader
readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader
readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
// Hyprland OnDemand grab: whitelist popout surfaces and bars so dismiss clicks still land.
HyprlandFocusGrab {
windows: {
const list = [];
if (root.contentWindow)
list.push(root.contentWindow);
if (root.backgroundWindow && root.backgroundWindow !== root.contentWindow)
list.push(root.backgroundWindow);
return list.concat(KeyboardFocus.barWindows);
}
active: KeyboardFocus.wantsGrab(root.shouldBeVisible, root.customKeyboardFocus)
}
Loader { Loader {
id: _fallbackContentLoader id: _fallbackContentLoader
@@ -127,8 +142,6 @@ Item {
return _usesConnectedBackendForScreen(targetScreen) ? connectedComp : standaloneComp; return _usesConnectedBackendForScreen(targetScreen) ? connectedComp : standaloneComp;
} }
// Defer Loader source-component swap until impl is fully closed; avoids
// tearing down a popout mid-animation when frame mode is toggled.
function _maybeResolveBackend() { function _maybeResolveBackend() {
_resolveBackendForScreen(screen); _resolveBackendForScreen(screen);
} }
+158 -181
View File
@@ -5,7 +5,6 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services import qs.Services
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
Item { Item {
id: root id: root
@@ -18,6 +17,7 @@ Item {
property Component overlayContent: null property Component overlayContent: null
property alias overlayLoader: overlayLoader property alias overlayLoader: overlayLoader
readonly property alias backgroundWindow: contentWindow readonly property alias backgroundWindow: contentWindow
readonly property alias contentWindow: contentWindow
property real popupWidth: 400 property real popupWidth: 400
property real popupHeight: 300 property real popupHeight: 300
property real triggerX: 0 property real triggerX: 0
@@ -38,8 +38,6 @@ Item {
property bool fullHeightSurface: false property bool fullHeightSurface: false
property bool _primeContent: false property bool _primeContent: false
property bool _resizeActive: false property bool _resizeActive: false
property string _chromeClaimId: ""
property int _connectedChromeSerial: 0
property real _chromeAnimTravelX: 1 property real _chromeAnimTravelX: 1
property real _chromeAnimTravelY: 1 property real _chromeAnimTravelY: 1
property bool _fullSyncQueued: false property bool _fullSyncQueued: false
@@ -55,7 +53,6 @@ Item {
"rightBar": 0 "rightBar": 0
}) })
property var screen: null property var screen: null
// Connected resize uses one full-screen surface; body-sized regions are masks.
readonly property bool useBackgroundWindow: false readonly property bool useBackgroundWindow: false
readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, { readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
"allow": ["top", "overlay"], "allow": ["top", "overlay"],
@@ -93,13 +90,49 @@ Item {
signal popoutClosed signal popoutClosed
signal backgroundClicked signal backgroundClicked
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
Timer { Timer {
id: _syncTimer id: _syncTimer
interval: 0 interval: 0
onTriggered: root._flushSync() onTriggered: root._flushSync()
} }
ConnectedSurfaceLease {
id: chromeLease
claimPrefix: root.layerNamespace
screenName: root.screen ? root.screen.name : ""
enabled: root.frameOwnsConnectedChrome
active: contentWindow.visible || root.shouldBeVisible
presented: contentWindow.visible || root.shouldBeVisible
renewTokenOnRecovery: false
isCurrentOwner: function(name) {
return PopoutManager.isCurrentPopout(root.popoutHandle, name);
}
hasOwner: function(_name, ownerId) {
return ConnectedModeState.hasPopoutOwner(ownerId);
}
statePresent: function(name, ownerId) {
return ConnectedModeState.hasPopoutOwner(ownerId) && ConnectedModeState.hasSurfaceDescriptor(name, "popout", ownerId);
}
claimState: function(_name, state, ownerId) {
return ConnectedModeState.claimPopout(ownerId, state);
}
ensureState: function(_name, state, ownerId) {
if (!ConnectedModeState.hasPopoutOwner(ownerId))
return false;
return ConnectedModeState.updatePopout(ownerId, state);
}
releaseState: function(_name, ownerId) {
return ConnectedModeState.releasePopout(ownerId);
}
updateAnimationState: function(_name, ownerId, animX, animY) {
return ConnectedModeState.setPopoutAnim(ownerId, animX, animY);
}
updateBodyState: function(_name, ownerId, bodyX, bodyY, bodyW, bodyH) {
return ConnectedModeState.setPopoutBody(ownerId, bodyX, bodyY, bodyW, bodyH);
}
onRecoveryRequested: root._queueFullSync()
}
property var _lastOpenedScreen: null property var _lastOpenedScreen: null
property bool isClosing: false property bool isClosing: false
@@ -169,11 +202,6 @@ Item {
setBarContext(pos, bottomGap); setBarContext(pos, bottomGap);
} }
function _nextChromeClaimId() {
_connectedChromeSerial += 1;
return layerNamespace + ":" + _connectedChromeSerial + ":" + (new Date()).getTime();
}
function _captureChromeAnimTravel() { function _captureChromeAnimTravel() {
_chromeAnimTravelX = Math.max(1, Math.abs(contentContainer.offsetX)); _chromeAnimTravelX = Math.max(1, Math.abs(contentContainer.offsetX));
_chromeAnimTravelY = Math.max(1, Math.abs(contentContainer.offsetY)); _chromeAnimTravelY = Math.max(1, Math.abs(contentContainer.offsetY));
@@ -203,15 +231,35 @@ Item {
function _connectedChromeState(visibleOverride) { function _connectedChromeState(visibleOverride) {
const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible; const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible;
const presented = contentWindow.visible || root.shouldBeVisible;
const phase = root.isClosing ? "closing" : (!presented ? "hidden" : (!contentWindow.visible && root.shouldBeVisible ? "opening" : "open"));
const bodyRect = {
"x": root.alignedX,
"y": root.renderedAlignedY,
"width": root.alignedWidth,
"height": root.renderedAlignedHeight
};
const animationOffset = {
"x": _connectedChromeAnimX(),
"y": _connectedChromeAnimY()
};
return { return {
"kind": "popout",
"screenName": root.screen ? root.screen.name : "",
"phase": phase,
"visible": visible, "visible": visible,
"presented": presented,
"barSide": contentContainer.connectedBarSide, "barSide": contentContainer.connectedBarSide,
"bodyRect": bodyRect,
"animationOffset": animationOffset,
"scale": 1,
"opacity": Theme.connectedSurfaceColor.a,
"bodyX": root.alignedX, "bodyX": root.alignedX,
"bodyY": root.renderedAlignedY, "bodyY": root.renderedAlignedY,
"bodyW": root.alignedWidth, "bodyW": root.alignedWidth,
"bodyH": root.renderedAlignedHeight, "bodyH": root.renderedAlignedHeight,
"animX": _connectedChromeAnimX(), "animX": animationOffset.x,
"animY": _connectedChromeAnimY(), "animY": animationOffset.y,
"screen": root.screen ? root.screen.name : "", "screen": root.screen ? root.screen.name : "",
"omitStartConnector": root._closeGapOmitStartConnector(), "omitStartConnector": root._closeGapOmitStartConnector(),
"omitEndConnector": root._closeGapOmitEndConnector() "omitEndConnector": root._closeGapOmitEndConnector()
@@ -219,34 +267,18 @@ Item {
} }
function _publishConnectedChromeState(forceClaim, visibleOverride) { function _publishConnectedChromeState(forceClaim, visibleOverride) {
if (!root.frameOwnsConnectedChrome || !root.screen || !_chromeClaimId) if (!root.frameOwnsConnectedChrome || !root.screen)
return false; return false;
return chromeLease.publish(_connectedChromeState(visibleOverride), !!forceClaim);
const screenName = root.screen.name;
const isCurrent = PopoutManager.isCurrentPopout(popoutHandle, screenName);
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
if (!isCurrent)
return false;
forceClaim = true;
} else if (forceClaim && !isCurrent) {
return false;
}
const state = _connectedChromeState(visibleOverride);
return forceClaim ? ConnectedModeState.claimPopout(_chromeClaimId, state) : ConnectedModeState.updatePopout(_chromeClaimId, state);
} }
function _releaseConnectedChromeState() { function _releaseConnectedChromeState() {
if (_chromeClaimId) chromeLease.release();
ConnectedModeState.releasePopout(_chromeClaimId);
_chromeClaimId = "";
} }
// Exposed animation state for ConnectedModeState
readonly property real contentAnimX: contentContainer.animX readonly property real contentAnimX: contentContainer.animX
readonly property real contentAnimY: contentContainer.animY readonly property real contentAnimY: contentContainer.animY
// ConnectedModeState sync
function _syncPopoutChromeState() { function _syncPopoutChromeState() {
if (!root.frameOwnsConnectedChrome) { if (!root.frameOwnsConnectedChrome) {
_releaseConnectedChromeState(); _releaseConnectedChromeState();
@@ -258,16 +290,11 @@ Item {
} }
if (!contentWindow.visible && !shouldBeVisible) if (!contentWindow.visible && !shouldBeVisible)
return; return;
if (!_chromeClaimId) { _publishConnectedChromeState(false);
if (!PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
return;
_chromeClaimId = _nextChromeClaimId();
}
_publishConnectedChromeState(!ConnectedModeState.hasPopoutOwner(_chromeClaimId));
} }
function _syncPopoutAnim(axis) { function _syncPopoutAnim(axis) {
if (!root.frameOwnsConnectedChrome || !_chromeClaimId) if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
return; return;
if (!contentWindow.visible && !shouldBeVisible) if (!contentWindow.visible && !shouldBeVisible)
return; return;
@@ -276,25 +303,15 @@ Item {
const syncY = axis === "y" && (barSide === "top" || barSide === "bottom"); const syncY = axis === "y" && (barSide === "top" || barSide === "bottom");
if (!syncX && !syncY) if (!syncX && !syncY)
return; return;
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) { chromeLease.updateAnim(syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
_queueFullSync();
return;
}
ConnectedModeState.setPopoutAnim(_chromeClaimId, syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
} }
function _syncPopoutBody() { function _syncPopoutBody() {
if (!root.frameOwnsConnectedChrome || !_chromeClaimId) if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
return; return;
if (!contentWindow.visible && !shouldBeVisible) if (!contentWindow.visible && !shouldBeVisible)
return; return;
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) { chromeLease.updateBody(root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
_queueFullSync();
return;
}
ConnectedModeState.setPopoutBody(_chromeClaimId, root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
} }
property bool _animSyncQueued: false property bool _animSyncQueued: false
@@ -345,25 +362,19 @@ Item {
Connections { Connections {
target: contentWindow target: contentWindow
function onVisibleChanged() { function onVisibleChanged() {
if (contentWindow.visible) { if (contentWindow.visible)
if (!root._chromeClaimId)
root._chromeClaimId = root._nextChromeClaimId();
root._publishConnectedChromeState(true); root._publishConnectedChromeState(true);
} else { else
root._releaseConnectedChromeState(); root._releaseConnectedChromeState();
} }
} }
}
Connections { Connections {
target: SettingsData target: SettingsData
function onConnectedFrameModeActiveChanged() { function onConnectedFrameModeActiveChanged() {
if (root.frameOwnsConnectedChrome) { if (root.frameOwnsConnectedChrome) {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) { if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name))
if (!root._chromeClaimId)
root._chromeClaimId = root._nextChromeClaimId();
root._publishConnectedChromeState(true); root._publishConnectedChromeState(true);
}
} else { } else {
root._releaseConnectedChromeState(); root._releaseConnectedChromeState();
} }
@@ -376,16 +387,17 @@ Item {
Connections { Connections {
target: ConnectedModeState target: ConnectedModeState
function onPopoutOwnerIdChanged() { function onPopoutOwnerIdChanged() {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name) && !ConnectedModeState.hasPopoutOwner(root._chromeClaimId)) chromeLease.checkOwnershipRecovery();
root._queueFullSync(); }
function onSurfaceDescriptorsChanged() {
chromeLease.checkStateRecovery();
} }
} }
Connections { Connections {
target: PopoutManager target: PopoutManager
function onPopoutChanged() { function onPopoutChanged() {
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) chromeLease.requestRecovery();
root._queueFullSync();
} }
} }
@@ -406,34 +418,26 @@ Item {
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen; const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) { if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false; contentWindow.visible = false;
} }
_lastOpenedScreen = screen; _lastOpenedScreen = screen;
PopoutManager.showPopout(popoutHandle); PopoutManager.showPopout(popoutHandle);
if (contentContainer) { if (contentContainer) {
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
if (!shouldBeVisible) if (!shouldBeVisible)
morph.openProgress = 0; morph.openProgress = 0;
_captureChromeAnimTravel(); _captureChromeAnimTravel();
} }
if (root.frameOwnsConnectedChrome) { if (root.frameOwnsConnectedChrome) {
_chromeClaimId = _nextChromeClaimId(); chromeLease.beginClaim();
_publishConnectedChromeState(true, true); _publishConnectedChromeState(true, true);
} else { } else {
_chromeClaimId = ""; chromeLease.release();
} }
if (screenChanged) { if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous // Unmap/remap wl_surface across ticks so blur republishes on the new screen.
// falsetrue visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
Qt.callLater(() => { Qt.callLater(() => {
if (!root.shouldBeVisible) if (!root.shouldBeVisible)
return; return;
@@ -552,6 +556,18 @@ Item {
return Math.abs(value - bound) <= Math.max(1, Theme.hairline(root.dpr) * 2); return Math.abs(value - bound) <= Math.max(1, Theme.hairline(root.dpr) * 2);
} }
// Snap positions within connector radius flush to the frame edge (avoids pinched arcs).
function _snapNearFrameBound(value, minBound, maxBound, minIsFrame, maxIsFrame) {
if (!root.usesConnectedSurfaceChrome || !root.closeFrameGapsActive)
return value;
const snapDist = Theme.connectedCornerRadius;
if (maxIsFrame && value < maxBound && maxBound - value < snapDist && maxBound - value <= value - minBound)
return maxBound;
if (minIsFrame && value > minBound && value - minBound < snapDist)
return minBound;
return value;
}
function _closeGapClampedToFrameSide(side) { function _closeGapClampedToFrameSide(side) {
if (!root.closeFrameGapsActive) if (!root.closeFrameGapsActive)
return false; return false;
@@ -607,7 +623,6 @@ Item {
property real renderedAlignedY: alignedY property real renderedAlignedY: alignedY
property real renderedAlignedHeight: alignedHeight property real renderedAlignedHeight: alignedHeight
readonly property bool renderedGeometryGrowing: alignedHeight >= renderedAlignedHeight readonly property bool renderedGeometryGrowing: alignedHeight >= renderedAlignedHeight
// Snap rendered geometry while the entrance morph runs so it doesn't ride a second animation (side-bar ramp).
readonly property bool _settlingToOpen: fullHeightSurface && shouldBeVisible && morphAnim.running readonly property bool _settlingToOpen: fullHeightSurface && shouldBeVisible && morphAnim.running
Behavior on renderedAlignedY { Behavior on renderedAlignedY {
@@ -657,8 +672,6 @@ Item {
return 0; return 0;
if (!root.usesConnectedSurfaceChrome) if (!root.usesConnectedSurfaceChrome)
return exclusion; return exclusion;
// In a shared frame corner, the adjacent connected bar already occupies
// one rounded-corner radius before the popout's own connector begins.
return exclusion + Theme.connectedCornerRadius * 2; return exclusion + Theme.connectedCornerRadius * 2;
} }
@@ -691,16 +704,16 @@ Item {
switch (effectiveBarPosition) { switch (effectiveBarPosition) {
case SettingsData.Position.Left: case SettingsData.Position.Left:
// bar on left: left side is bar-adjacent (popupGap), right side is frame-perpendicular (edgeGap)
return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGapRight, anchorX)); return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGapRight, anchorX));
case SettingsData.Position.Right: case SettingsData.Position.Right:
// bar on right: right side is bar-adjacent (popupGap), left side is frame-perpendicular (edgeGap)
return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth)); return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth));
default: default:
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2); const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
const minX = Math.max(edgeGapLeft, adjacentBarClearance(adjacentBarInfo.leftBar)); const clearLeft = adjacentBarClearance(adjacentBarInfo.leftBar);
const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, adjacentBarClearance(adjacentBarInfo.rightBar)); const clearRight = adjacentBarClearance(adjacentBarInfo.rightBar);
return Math.max(minX, Math.min(maxX, rawX)); const minX = Math.max(edgeGapLeft, clearLeft);
const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, clearRight);
return _snapNearFrameBound(Math.max(minX, Math.min(maxX, rawX)), minX, maxX, edgeGapLeft >= clearLeft, edgeGapRight >= clearRight);
} }
})(), dpr) })(), dpr)
@@ -712,44 +725,34 @@ Item {
switch (effectiveBarPosition) { switch (effectiveBarPosition) {
case SettingsData.Position.Bottom: case SettingsData.Position.Bottom:
// bar on bottom: bottom side is bar-adjacent (popupGap), top side is frame-perpendicular (edgeGap)
return Math.max(edgeGapTop, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight)); return Math.max(edgeGapTop, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight));
case SettingsData.Position.Top: case SettingsData.Position.Top:
// bar on top: top side is bar-adjacent (popupGap), bottom side is frame-perpendicular (edgeGap)
return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY)); return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY));
default: default:
const rawY = triggerY - (popupHeight / 2); const rawY = triggerY - (popupHeight / 2);
const minY = Math.max(edgeGapTop, adjacentBarClearance(adjacentBarInfo.topBar)); const clearTop = adjacentBarClearance(adjacentBarInfo.topBar);
const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, adjacentBarClearance(adjacentBarInfo.bottomBar)); const clearBottom = adjacentBarClearance(adjacentBarInfo.bottomBar);
return Math.max(minY, Math.min(maxY, rawY)); const minY = Math.max(edgeGapTop, clearTop);
const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, clearBottom);
return _snapNearFrameBound(Math.max(minY, Math.min(maxY, rawY)), minY, maxY, edgeGapTop >= clearTop, edgeGapBottom >= clearBottom);
} }
})(), dpr) })(), dpr)
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 readonly property real maskX: _dismissZone.x
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 readonly property real maskY: _dismissZone.y
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 readonly property real maskWidth: _dismissZone.width
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 readonly property real maskHeight: _dismissZone.height
readonly property real maskX: { DismissZone {
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; id: _dismissZone
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); barPosition: root.effectiveBarPosition
} barX: root.barX
barY: root.barY
readonly property real maskY: { barWidth: root.barWidth
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; barHeight: root.barHeight
return Math.max(triggeringBarTopExclusion, adjacentTopBar); screenWidth: root.screenWidth
} screenHeight: root.screenHeight
adjacentBarInfo: root.adjacentBarInfo
readonly property real maskWidth: {
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
return Math.max(100, screenWidth - maskX - rightExclusion);
}
readonly property real maskHeight: {
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
return Math.max(100, screenHeight - maskY - bottomExclusion);
} }
PanelWindow { PanelWindow {
@@ -764,10 +767,8 @@ Item {
blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, contentContainer.scaleValue) readonly property real s: Math.min(1, contentContainer.scaleValue)
readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome || Theme.isDirectionalEffect readonly property bool trackBlurFromBarEdge: root.usesConnectedSurfaceChrome
// Directional popouts clip to the bar edge, so the blur needs to grow from
// that same edge instead of translating through the bar before settling.
readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0 readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0
readonly property real _dxClamp: (contentContainer.barLeft || contentContainer.barRight) ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0 readonly property real _dxClamp: (contentContainer.barLeft || contentContainer.barRight) ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0
@@ -781,17 +782,7 @@ Item {
WlrLayershell.namespace: root.layerNamespace WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.layer: root.effectivePopoutLayer
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
readonly property bool _fullHeight: root.fullHeightSurface readonly property bool _fullHeight: root.fullHeightSurface
anchors { anchors {
@@ -813,7 +804,6 @@ Item {
Region { Region {
id: contentInputMask id: contentInputMask
// Use bar-aware mask so bar widget clicks pass through when a popout is open.
item: (shouldBeVisible && backgroundInteractive) ? backgroundDismissalMask : contentMaskRect item: (shouldBeVisible && backgroundInteractive) ? backgroundDismissalMask : contentMaskRect
} }
@@ -924,7 +914,6 @@ Item {
readonly property real computedScaleCollapsed: root.animationScaleCollapsed readonly property real computedScaleCollapsed: root.animationScaleCollapsed
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
QtObject { QtObject {
id: morph id: morph
property real openProgress: 0 property real openProgress: 0
@@ -971,7 +960,6 @@ Item {
clip: shouldClip clip: shouldClip
// Bound the clipping strictly to the bar side, allowing massive overflow on the other 3 sides for shadows
x: shouldClip ? (contentContainer.barLeft ? -connectedClipAllowance : -clipOversize) : 0 x: shouldClip ? (contentContainer.barLeft ? -connectedClipAllowance : -clipOversize) : 0
y: shouldClip ? (contentContainer.barTop ? -connectedClipAllowance : -clipOversize) : 0 y: shouldClip ? (contentContainer.barTop ? -connectedClipAllowance : -clipOversize) : 0
@@ -1008,22 +996,13 @@ Item {
ElevationShadow { ElevationShadow {
id: shadowSource id: shadowSource
readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0 visible: !root.usesConnectedSurfaceChrome
readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 width: rollOutAdjuster.baseWidth
readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 height: rollOutAdjuster.baseHeight
readonly property real extraTop: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0
readonly property real extraBottom: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0
readonly property real bodyX: extraLeft
readonly property real bodyY: extraTop
readonly property real bodyWidth: rollOutAdjuster.baseWidth
readonly property real bodyHeight: rollOutAdjuster.baseHeight
width: rollOutAdjuster.baseWidth + extraLeft + extraRight
height: rollOutAdjuster.baseHeight + extraTop + extraBottom
opacity: contentWrapper.publishedOpacity opacity: contentWrapper.publishedOpacity
scale: contentWrapper.scale scale: contentWrapper.scale
x: contentWrapper.x - extraLeft x: contentWrapper.x
y: contentWrapper.y - extraTop y: contentWrapper.y
level: root.shadowLevel level: root.shadowLevel
direction: root.effectiveShadowDirection direction: root.effectiveShadowDirection
fallbackOffset: root.shadowFallbackOffset fallbackOffset: root.shadowFallbackOffset
@@ -1035,49 +1014,49 @@ Item {
targetColor: contentContainer.surfaceColor targetColor: contentContainer.surfaceColor
borderColor: contentContainer.surfaceBorderColor borderColor: contentContainer.surfaceBorderColor
borderWidth: contentContainer.surfaceBorderWidth borderWidth: contentContainer.surfaceBorderWidth
useCustomSource: root.usesConnectedSurfaceChrome
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome
}
Item { Item {
anchors.fill: parent id: localChrome
visible: root.usesLocalConnectedSurfaceChrome visible: root.usesLocalConnectedSurfaceChrome
clip: false
Rectangle { readonly property real extraLeft: (contentContainer.barTop || contentContainer.barBottom) ? Theme.connectedCornerRadius : 0
x: shadowSource.bodyX readonly property real extraTop: (contentContainer.barLeft || contentContainer.barRight) ? Theme.connectedCornerRadius : 0
y: shadowSource.bodyY
width: shadowSource.bodyWidth
height: shadowSource.bodyHeight
topLeftRadius: contentContainer.surfaceTopLeftRadius
topRightRadius: contentContainer.surfaceTopRightRadius
bottomLeftRadius: contentContainer.surfaceBottomLeftRadius
bottomRightRadius: contentContainer.surfaceBottomRightRadius
color: contentContainer.surfaceColor
}
ConnectedCorner { readonly property bool shadowsOn: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
visible: root.usesConnectedSurfaceChrome readonly property real shadowBlurPx: root.shadowLevel && root.shadowLevel.blurPx !== undefined ? root.shadowLevel.blurPx : 0
barSide: contentContainer.connectedBarSide readonly property real shadowSpreadPx: root.shadowLevel && root.shadowLevel.spreadPx !== undefined ? root.shadowLevel.spreadPx : 0
placement: "left" readonly property real shadowOffsetX: Theme.elevationOffsetXFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
spacing: 0 readonly property real shadowOffsetY: Theme.elevationOffsetYFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
connectorRadius: Theme.connectedCornerRadius readonly property color shadowTint: Theme.elevationShadowColor(root.shadowLevel)
color: contentContainer.surfaceColor readonly property var ambient: Theme.elevationAmbient(root.shadowLevel)
dpr: root.dpr readonly property real pad: shadowsOn ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), ambient.blurPx + ambient.spreadPx) + 2) : 0
x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr)
y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr)
}
ConnectedCorner { width: rollOutAdjuster.baseWidth + extraLeft * 2
visible: root.usesConnectedSurfaceChrome height: rollOutAdjuster.baseHeight + extraTop * 2
barSide: contentContainer.connectedBarSide opacity: contentWrapper.publishedOpacity
placement: "right" scale: contentWrapper.scale
spacing: 0 x: contentWrapper.x - extraLeft
connectorRadius: Theme.connectedCornerRadius y: contentWrapper.y - extraTop
color: contentContainer.surfaceColor
dpr: root.dpr ShaderEffect {
x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr) anchors.fill: parent
y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr) anchors.topMargin: contentContainer.barTop ? 0 : -localChrome.pad
} anchors.bottomMargin: contentContainer.barBottom ? 0 : -localChrome.pad
anchors.leftMargin: contentContainer.barLeft ? 0 : -localChrome.pad
anchors.rightMargin: contentContainer.barRight ? 0 : -localChrome.pad
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/connected_chrome.frag.qsb")
property real widthPx: width
property real heightPx: height
property vector4d surfaceColor: Qt.vector4d(contentContainer.surfaceColor.r, contentContainer.surfaceColor.g, contentContainer.surfaceColor.b, contentContainer.surfaceColor.a)
property vector4d shadowColor: Qt.vector4d(localChrome.shadowTint.r, localChrome.shadowTint.g, localChrome.shadowTint.b, localChrome.shadowsOn ? localChrome.shadowTint.a : 0)
property vector4d shadowParam: Qt.vector4d(Math.max(0, localChrome.shadowBlurPx), localChrome.shadowSpreadPx, localChrome.shadowOffsetX, localChrome.shadowOffsetY)
property vector4d ambientParam: Qt.vector4d(localChrome.ambient.blurPx, localChrome.ambient.spreadPx, localChrome.shadowsOn ? localChrome.ambient.alpha : 0, 0)
property vector4d bodyRect: Qt.vector4d((contentContainer.barLeft ? 0 : localChrome.pad) + localChrome.extraLeft, (contentContainer.barTop ? 0 : localChrome.pad) + localChrome.extraTop, rollOutAdjuster.baseWidth, rollOutAdjuster.baseHeight)
property vector4d cornerRadius: Qt.vector4d(contentContainer.surfaceTopLeftRadius, contentContainer.surfaceTopRightRadius, contentContainer.surfaceBottomRightRadius, contentContainer.surfaceBottomLeftRadius)
property vector4d edgeParam: Qt.vector4d(contentContainer.barTop ? 0 : (contentContainer.barBottom ? 1 : (contentContainer.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
} }
} }
@@ -1135,8 +1114,6 @@ Item {
Connections { Connections {
target: contentWindow target: contentWindow
function onVisibleChanged() { function onVisibleChanged() {
// open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible) if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false; contentWrapper._renderActive = false;
} }
+28 -41
View File
@@ -18,6 +18,7 @@ Item {
property Component overlayContent: null property Component overlayContent: null
property alias overlayLoader: overlayLoader property alias overlayLoader: overlayLoader
readonly property alias backgroundWindow: backgroundWindow readonly property alias backgroundWindow: backgroundWindow
readonly property alias contentWindow: contentWindow
property real popupWidth: 400 property real popupWidth: 400
property real popupHeight: 300 property real popupHeight: 300
property real triggerX: 0 property real triggerX: 0
@@ -494,31 +495,21 @@ Item {
} }
})(), dpr) })(), dpr)
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 readonly property real maskX: _dismissZone.x
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 readonly property real maskY: _dismissZone.y
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 readonly property real maskWidth: _dismissZone.width
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 readonly property real maskHeight: _dismissZone.height
readonly property real maskX: { DismissZone {
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; id: _dismissZone
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); barPosition: root.effectiveBarPosition
} barX: root.barX
barY: root.barY
readonly property real maskY: { barWidth: root.barWidth
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; barHeight: root.barHeight
return Math.max(triggeringBarTopExclusion, adjacentTopBar); screenWidth: root.screenWidth
} screenHeight: root.screenHeight
adjacentBarInfo: root.adjacentBarInfo
readonly property real maskWidth: {
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar);
return Math.max(100, screenWidth - maskX - rightExclusion);
}
readonly property real maskHeight: {
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar);
return Math.max(100, screenHeight - maskY - bottomExclusion);
} }
PanelWindow { PanelWindow {
@@ -598,31 +589,25 @@ Item {
id: popoutBlur id: popoutBlur
targetWindow: contentWindow targetWindow: contentWindow
readonly property real s: Math.min(1, contentContainer.scaleValue) readonly property real s: Math.min(1, contentContainer.scaleValue)
readonly property bool trackBlurFromBarEdge: root.fluidStandaloneActive
readonly property real op: Math.max(0, Math.min(1, (morph.openProgress - 0.08) * 1.6)) readonly property real op: Math.max(0, Math.min(1, (morph.openProgress - 0.08) * 1.6))
readonly property bool blurAlive: trackBlurFromBarEdge ? (contentContainer.revealWidth > 0 && contentContainer.revealHeight > 0) : root.shouldBeVisible readonly property bool revealClipActive: root.fluidStandaloneActive
blurX: trackBlurFromBarEdge ? contentContainer.x + contentContainer.revealX : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) blurX: revealClipActive ? contentContainer.x : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: trackBlurFromBarEdge ? contentContainer.y + contentContainer.revealY : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) blurY: revealClipActive ? contentContainer.y : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealWidth : contentContainer.width * s * op) : 0 blurWidth: root.shouldBeVisible ? (revealClipActive ? contentContainer.width : contentContainer.width * s * op) : 0
blurHeight: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealHeight : contentContainer.height * s * op) : 0 blurHeight: root.shouldBeVisible ? (revealClipActive ? contentContainer.height : contentContainer.height * s * op) : 0
blurRadius: Theme.cornerRadius blurRadius: Theme.cornerRadius
clipEnabled: revealClipActive
clipX: contentContainer.x + contentContainer.revealX
clipY: contentContainer.y + contentContainer.revealY
clipWidth: root.shouldBeVisible ? contentContainer.revealWidth : 0
clipHeight: root.shouldBeVisible ? contentContainer.revealHeight : 0
} }
WlrLayershell.namespace: root.layerNamespace WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.layer: root.effectivePopoutLayer
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
if (PopoutManager.screenshotActive)
return WlrKeyboardFocus.None;
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
anchors { anchors {
left: true left: true
@@ -721,6 +706,8 @@ Item {
QtObject { QtObject {
id: morph id: morph
property real openProgress: 0 property real openProgress: 0
onOpenProgressChanged: if (root.fluidStandaloneActive)
root._kickBlurCommit()
Behavior on openProgress { Behavior on openProgress {
enabled: root.animationsEnabled enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
+25
View File
@@ -0,0 +1,25 @@
import QtQuick
import qs.Common
QtObject {
id: root
property int barPosition: 0
property real barX: 0
property real barY: 0
property real barWidth: 0
property real barHeight: 0
property real screenWidth: 0
property real screenHeight: 0
property var adjacentBarInfo: null
readonly property real _leftExclusion: (barPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
readonly property real _topExclusion: (barPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
readonly property real _rightExclusion: (barPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
readonly property real _bottomExclusion: (barPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
readonly property real x: Math.max(_leftExclusion, adjacentBarInfo?.leftBar ?? 0)
readonly property real y: Math.max(_topExclusion, adjacentBarInfo?.topBar ?? 0)
readonly property real width: Math.max(100, screenWidth - x - Math.max(_rightExclusion, adjacentBarInfo?.rightBar ?? 0))
readonly property real height: Math.max(100, screenHeight - y - Math.max(_bottomExclusion, adjacentBarInfo?.bottomBar ?? 0))
}
+14 -4
View File
@@ -16,6 +16,11 @@ Item {
property real blurWidth: 0 property real blurWidth: 0
property real blurHeight: 0 property real blurHeight: 0
property real blurRadius: 0 property real blurRadius: 0
property bool clipEnabled: false
property real clipX: blurX
property real clipY: blurY
property real clipWidth: blurWidth
property real clipHeight: blurHeight
readonly property bool _active: blurEnabled && BlurService.enabled && !!targetWindow readonly property bool _active: blurEnabled && BlurService.enabled && !!targetWindow
@@ -26,6 +31,14 @@ Item {
width: root.blurWidth width: root.blurWidth
height: root.blurHeight height: root.blurHeight
radius: root.blurRadius radius: root.blurRadius
Region {
intersection: Intersection.Intersect
x: root.clipEnabled ? root.clipX : root.blurX
y: root.clipEnabled ? root.clipY : root.blurY
width: root.clipEnabled ? root.clipWidth : root.blurWidth
height: root.clipEnabled ? root.clipHeight : root.blurHeight
}
} }
function _apply() { function _apply() {
@@ -39,10 +52,7 @@ Item {
targetWindow.BackgroundEffect.blurRegion = null; targetWindow.BackgroundEffect.blurRegion = null;
} }
// Force BackgroundEffect to re-publish the blur region on the current wl_surface. // Re-publish blur region after wl_surface remaps (e.g. screen change).
// Clearing first bypasses Quickshell's same-Region dedup in BackgroundEffect::setBlurRegion,
// setting pendingBlurRegion=true so the next polish actually ships the region needed
// when the underlying surface has been remapped (e.g. PanelWindow.screen change).
function kick() { function kick() {
if (!targetWindow) if (!targetWindow)
return; return;