mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-13 14:36:32 -04:00
refactor(framemode): unify connected surface chrome via SDF pipeline
- Shadow system rewrite with SDF quads - Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders - Improve frame blur performance - Plugin performance gate
This commit is contained in:
@@ -7,6 +7,7 @@ Item {
|
||||
|
||||
required property var modalHandle
|
||||
required property string claimPrefix
|
||||
property string surfaceKind: "modal"
|
||||
property string screenName: ""
|
||||
property bool enabled: false
|
||||
property bool active: false
|
||||
@@ -14,112 +15,97 @@ Item {
|
||||
property bool dockBlocked: false
|
||||
property string dockSide: ""
|
||||
|
||||
property string claimId: ""
|
||||
property string claimedScreenName: ""
|
||||
property alias claimId: lease.claimId
|
||||
property alias claimedScreenName: lease.claimedScreenName
|
||||
|
||||
signal recoveryRequested
|
||||
|
||||
visible: false
|
||||
|
||||
function _nextClaimId() {
|
||||
return claimPrefix + ":" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
|
||||
}
|
||||
|
||||
function _isCurrentModal(name) {
|
||||
return !!name && ModalManager.isCurrentModal(modalHandle, name);
|
||||
}
|
||||
|
||||
function _shouldRecover() {
|
||||
return active && enabled && _isCurrentModal(screenName);
|
||||
}
|
||||
|
||||
function _requestRecovery() {
|
||||
if (_shouldRecover())
|
||||
recoveryRequested();
|
||||
ConnectedSurfaceLease {
|
||||
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) {
|
||||
return ConnectedModeState.hasModalOwner(name, ownerId);
|
||||
}
|
||||
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) {
|
||||
if (!enabled || !screenName || !state) {
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
if (claimedScreenName && claimedScreenName !== screenName)
|
||||
release();
|
||||
|
||||
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;
|
||||
return lease.publish(Object.assign({}, state, {
|
||||
"kind": root.surfaceKind,
|
||||
"screenName": root.screenName,
|
||||
"presented": root.presented,
|
||||
"dockRetractSide": root.dockBlocked ? root.dockSide : ""
|
||||
}), false);
|
||||
}
|
||||
|
||||
function updateAnim(animX, animY) {
|
||||
if (!enabled || !claimId || !claimedScreenName)
|
||||
return false;
|
||||
if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) {
|
||||
_requestRecovery();
|
||||
return false;
|
||||
}
|
||||
return ConnectedModeState.setModalAnim(claimedScreenName, animX, animY, claimId);
|
||||
return lease.updateAnim(animX, animY);
|
||||
}
|
||||
|
||||
function updateBody(bodyX, bodyY, bodyW, bodyH) {
|
||||
if (!enabled || !claimId || !claimedScreenName)
|
||||
return false;
|
||||
if (!ConnectedModeState.hasModalOwner(claimedScreenName, claimId)) {
|
||||
_requestRecovery();
|
||||
return false;
|
||||
}
|
||||
return ConnectedModeState.setModalBody(claimedScreenName, bodyX, bodyY, bodyW, bodyH, claimId);
|
||||
return lease.updateBody(bodyX, bodyY, bodyW, bodyH);
|
||||
}
|
||||
|
||||
function release() {
|
||||
if (!claimId)
|
||||
return;
|
||||
ConnectedModeState.releaseDockRetract(claimId);
|
||||
const releasedClaimId = claimId;
|
||||
const releasedScreenName = claimedScreenName;
|
||||
claimId = "";
|
||||
claimedScreenName = "";
|
||||
if (releasedScreenName)
|
||||
ConnectedModeState.clearModalState(releasedScreenName, releasedClaimId);
|
||||
return lease.release();
|
||||
}
|
||||
|
||||
Component.onDestruction: release()
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onModalChanged() {
|
||||
root._requestRecovery();
|
||||
lease.requestRecovery();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ConnectedModeState
|
||||
function onModalOwnersChanged() {
|
||||
if (!ConnectedModeState.hasModalOwner(root.screenName, root.claimId))
|
||||
root._requestRecovery();
|
||||
lease.checkOwnershipRecovery();
|
||||
}
|
||||
function onModalStatesChanged() {
|
||||
if (!ConnectedModeState.modalStates[root.screenName])
|
||||
root._requestRecovery();
|
||||
lease.checkStateRecovery();
|
||||
}
|
||||
function onSurfaceDescriptorsChanged() {
|
||||
lease.checkStateRecovery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,123 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import "ConnectedSurfaceDescriptor.js" as SurfaceDescriptor
|
||||
|
||||
Singleton {
|
||||
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: ({
|
||||
"reveal": false,
|
||||
"barSide": "bottom",
|
||||
@@ -18,7 +131,6 @@ Singleton {
|
||||
"slideY": 0
|
||||
})
|
||||
|
||||
// Popout state (updated by DankPopout when connectedFrameModeActive)
|
||||
property string popoutOwnerId: ""
|
||||
property bool popoutVisible: false
|
||||
property string popoutBarSide: "top"
|
||||
@@ -32,14 +144,10 @@ Singleton {
|
||||
property bool popoutOmitStartConnector: false
|
||||
property bool popoutOmitEndConnector: false
|
||||
|
||||
// Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name
|
||||
property var dockStates: ({})
|
||||
|
||||
// Dock slide offsets — hot-path updates separated from full geometry state
|
||||
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: ({})
|
||||
|
||||
function _cloneDict(src) {
|
||||
@@ -69,8 +177,10 @@ Singleton {
|
||||
popoutOwnerId = claimId;
|
||||
const ok = updatePopout(claimId, state);
|
||||
if (ok) {
|
||||
if (previousScreen && previousScreen !== popoutScreen)
|
||||
if (previousScreen && previousScreen !== popoutScreen) {
|
||||
_clearSurfaceDescriptor(previousScreen, "popout");
|
||||
_bumpSurfaceRevision(previousScreen);
|
||||
}
|
||||
_bumpSurfaceRevision(popoutScreen);
|
||||
}
|
||||
return ok;
|
||||
@@ -103,6 +213,21 @@ Singleton {
|
||||
if (state.omitEndConnector !== undefined)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -123,6 +248,7 @@ Singleton {
|
||||
popoutScreen = "";
|
||||
popoutOmitStartConnector = false;
|
||||
popoutOmitEndConnector = false;
|
||||
_clearSurfaceDescriptor(releasedScreen, "popout", claimId);
|
||||
_bumpSurfaceRevision(releasedScreen);
|
||||
return true;
|
||||
}
|
||||
@@ -193,13 +319,21 @@ Singleton {
|
||||
return false;
|
||||
|
||||
const normalized = _normalizeDockState(state);
|
||||
if (_sameDockState(dockStates[screenName], normalized))
|
||||
return true;
|
||||
const descriptorState = Object.assign({}, state, normalized, {
|
||||
"kind": "dock",
|
||||
"screenName": screenName,
|
||||
"visible": normalized.reveal,
|
||||
"presented": normalized.reveal,
|
||||
"phase": normalized.reveal ? (state.phase || "open") : "hidden"
|
||||
});
|
||||
const previous = dockStates[screenName] || emptyDockState;
|
||||
|
||||
const next = _cloneDict(dockStates);
|
||||
next[screenName] = normalized;
|
||||
dockStates = next;
|
||||
const stateChanged = !_sameDockState(dockStates[screenName], normalized);
|
||||
if (stateChanged) {
|
||||
const next = _cloneDict(dockStates);
|
||||
next[screenName] = normalized;
|
||||
dockStates = next;
|
||||
}
|
||||
_setSurfaceDescriptor(screenName, "dock", descriptorState, "dock:" + screenName);
|
||||
if (!!previous.reveal !== !!normalized.reveal)
|
||||
_bumpSurfaceRevision(screenName);
|
||||
return true;
|
||||
@@ -212,8 +346,8 @@ Singleton {
|
||||
const next = _cloneDict(dockStates);
|
||||
delete next[screenName];
|
||||
dockStates = next;
|
||||
_clearSurfaceDescriptor(screenName, "dock");
|
||||
|
||||
// Also clear corresponding slide
|
||||
if (dockSlides[screenName]) {
|
||||
const nextSlides = _cloneDict(dockSlides);
|
||||
delete nextSlides[screenName];
|
||||
@@ -283,13 +417,20 @@ Singleton {
|
||||
return false;
|
||||
|
||||
const normalized = _normalizeNotificationState(state);
|
||||
if (_sameNotificationState(notificationStates[screenName], normalized))
|
||||
return true;
|
||||
const descriptorState = Object.assign({}, state, normalized, {
|
||||
"kind": "notification",
|
||||
"screenName": screenName,
|
||||
"presented": normalized.visible,
|
||||
"phase": normalized.visible ? (state.phase || "open") : "hidden"
|
||||
});
|
||||
const previous = notificationStates[screenName] || emptyNotificationState;
|
||||
|
||||
const next = _cloneDict(notificationStates);
|
||||
next[screenName] = normalized;
|
||||
notificationStates = next;
|
||||
const stateChanged = !_sameNotificationState(notificationStates[screenName], normalized);
|
||||
if (stateChanged) {
|
||||
const next = _cloneDict(notificationStates);
|
||||
next[screenName] = normalized;
|
||||
notificationStates = next;
|
||||
}
|
||||
_setSurfaceDescriptor(screenName, "notification", descriptorState, "notification:" + screenName);
|
||||
if (!!previous.visible !== !!normalized.visible)
|
||||
_bumpSurfaceRevision(screenName);
|
||||
return true;
|
||||
@@ -302,11 +443,11 @@ Singleton {
|
||||
const next = _cloneDict(notificationStates);
|
||||
delete next[screenName];
|
||||
notificationStates = next;
|
||||
_clearSurfaceDescriptor(screenName, "notification");
|
||||
_bumpSurfaceRevision(screenName);
|
||||
return true;
|
||||
}
|
||||
|
||||
// DankModal / DankLauncherV2Modal State
|
||||
readonly property var emptyModalState: ({
|
||||
"visible": false,
|
||||
"barSide": "bottom",
|
||||
@@ -362,6 +503,10 @@ Singleton {
|
||||
const next = _cloneDict(modalStates);
|
||||
next[screenName] = normalized;
|
||||
modalStates = next;
|
||||
_setSurfaceDescriptor(screenName, "modal", Object.assign({}, state, normalized, {
|
||||
"kind": state.kind || "modal",
|
||||
"screenName": screenName
|
||||
}), ownerId || "");
|
||||
_bumpSurfaceRevision(screenName);
|
||||
return true;
|
||||
}
|
||||
@@ -372,11 +517,16 @@ Singleton {
|
||||
if (ownerId && modalOwners[screenName] !== ownerId)
|
||||
return false;
|
||||
const normalized = _normalizeModalState(state);
|
||||
if (_sameModalState(modalStates[screenName], normalized))
|
||||
return true;
|
||||
const next = _cloneDict(modalStates);
|
||||
next[screenName] = normalized;
|
||||
modalStates = next;
|
||||
const descriptorState = Object.assign({}, state, normalized, {
|
||||
"kind": state.kind || (surfaceDescriptor(screenName, "modal").kind || "modal"),
|
||||
"screenName": screenName
|
||||
});
|
||||
if (!_sameModalState(modalStates[screenName], normalized)) {
|
||||
const next = _cloneDict(modalStates);
|
||||
next[screenName] = normalized;
|
||||
modalStates = next;
|
||||
}
|
||||
_setSurfaceDescriptor(screenName, "modal", descriptorState, ownerId || modalOwners[screenName] || "");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -395,10 +545,6 @@ Singleton {
|
||||
return updateModalState(screenName, state, ownerId);
|
||||
}
|
||||
|
||||
function setModalState(screenName, state) {
|
||||
return updateModalState(screenName, state, null);
|
||||
}
|
||||
|
||||
function clearModalState(screenName, ownerId) {
|
||||
if (!screenName)
|
||||
return false;
|
||||
@@ -418,6 +564,7 @@ Singleton {
|
||||
delete nextOwners[screenName];
|
||||
modalOwners = nextOwners;
|
||||
}
|
||||
_clearSurfaceDescriptor(screenName, "modal", ownerId);
|
||||
_bumpSurfaceRevision(screenName);
|
||||
return true;
|
||||
}
|
||||
@@ -501,9 +648,6 @@ Singleton {
|
||||
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() {
|
||||
const live = {};
|
||||
const screens = Quickshell.screens || [];
|
||||
@@ -543,6 +687,9 @@ Singleton {
|
||||
const nextSurfaceRevisions = pruneKeyed(surfaceRevisions);
|
||||
if (nextSurfaceRevisions !== null)
|
||||
surfaceRevisions = nextSurfaceRevisions;
|
||||
const nextDescriptors = pruneKeyed(surfaceDescriptors);
|
||||
if (nextDescriptors !== null)
|
||||
surfaceDescriptors = nextDescriptors;
|
||||
|
||||
let retractChanged = false;
|
||||
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;
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
@@ -19,7 +18,11 @@ Item {
|
||||
property real bottomRightRadius: targetRadius
|
||||
property color borderColor: "transparent"
|
||||
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 real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0
|
||||
@@ -28,36 +31,24 @@ Item {
|
||||
property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset)
|
||||
property color shadowColor: Theme.elevationShadowColor(level)
|
||||
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
|
||||
|
||||
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
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
visible: !root.useCustomSource
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomLeftRadius: root.bottomLeftRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
color: root.targetColor
|
||||
border.color: root.borderColor
|
||||
border.width: root.borderWidth
|
||||
anchors.margins: -root._pad
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/elevation_rect.frag.qsb")
|
||||
|
||||
property real widthPx: width
|
||||
property real heightPx: height
|
||||
property real borderWidth: root.borderWidth
|
||||
property vector4d rectPx: Qt.vector4d(root._pad + root.sourceX, root._pad + root.sourceY, root.sourceWidth, root.sourceHeight)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -911,6 +911,16 @@ Singleton {
|
||||
}
|
||||
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) {
|
||||
if (!level)
|
||||
return 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
@@ -11,11 +10,6 @@ DankModal {
|
||||
|
||||
layerNamespace: "dms:bluetooth-pairing"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property string deviceName: ""
|
||||
property string deviceAddress: ""
|
||||
property string requestType: ""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Common
|
||||
@@ -12,11 +11,6 @@ DankModal {
|
||||
|
||||
layerNamespace: "dms:clipboard"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [clipboardHistoryModal.contentWindow]
|
||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible) {
|
||||
hide();
|
||||
@@ -64,6 +58,7 @@ DankModal {
|
||||
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
||||
|
||||
visible: false
|
||||
keepContentLoaded: true
|
||||
modalWidth: ClipboardConstants.modalWidth
|
||||
modalHeight: ClipboardConstants.modalHeight
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -52,8 +53,13 @@ Item {
|
||||
focus: true
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Hyprland OnDemand grab delivers keyboard focus to the modal content surface.
|
||||
HyprlandFocusGrab {
|
||||
windows: root.contentWindow ? [root.contentWindow] : []
|
||||
active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus)
|
||||
}
|
||||
readonly property var contentWindow: impl.item ? impl.item.contentWindow : null
|
||||
readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null
|
||||
readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null
|
||||
readonly property real screenWidth: impl.item ? impl.item.screenWidth : 1920
|
||||
readonly property real screenHeight: impl.item ? impl.item.screenHeight : 1080
|
||||
@@ -96,8 +102,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Defer Loader source-component swap until impl is fully closed; avoids
|
||||
// tearing down a modal mid-animation when frame mode is toggled.
|
||||
function _maybeResolveBackend() {
|
||||
if (_resolvedBackend === _desiredBackend)
|
||||
return;
|
||||
|
||||
@@ -31,7 +31,6 @@ Item {
|
||||
property bool closeOnBackgroundClick: true
|
||||
property string animationType: "scale"
|
||||
|
||||
// Opposite side from the launcher by default; subclasses may override
|
||||
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
|
||||
|
||||
readonly property bool frameConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && !!effectiveScreen && SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
|
||||
@@ -87,16 +86,13 @@ Item {
|
||||
property real frozenMotionOffsetX: 0
|
||||
property real frozenMotionOffsetY: 0
|
||||
readonly property alias contentWindow: contentWindow
|
||||
readonly property alias clickCatcher: clickCatcher
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
readonly property bool useBackground: false
|
||||
readonly property bool useSingleWindow: CompositorService.isHyprland
|
||||
|
||||
signal opened
|
||||
signal dialogClosed
|
||||
signal backgroundClicked
|
||||
|
||||
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
||||
Timer {
|
||||
id: _syncTimer
|
||||
interval: 0
|
||||
@@ -115,6 +111,7 @@ Item {
|
||||
id: modalChrome
|
||||
modalHandle: root.modalHandle
|
||||
claimPrefix: root.layerNamespace + ":modal"
|
||||
surfaceKind: "modal"
|
||||
screenName: root._currentScreenName()
|
||||
enabled: root.frameOwnsConnectedChrome
|
||||
active: root.shouldBeVisible
|
||||
@@ -125,17 +122,38 @@ Item {
|
||||
}
|
||||
|
||||
function _publishModalChromeState() {
|
||||
const presented = shouldBeVisible || contentWindow.visible;
|
||||
const phase = !presented ? "hidden" : (!shouldBeVisible && contentWindow.visible ? "closing" : (!contentWindow.visible ? "opening" : "open"));
|
||||
const bodyRect = {
|
||||
"x": alignedX,
|
||||
"y": alignedY,
|
||||
"width": alignedWidth,
|
||||
"height": alignedHeight
|
||||
};
|
||||
const animationOffset = {
|
||||
"x": modalContainer ? modalContainer.animX : 0,
|
||||
"y": modalContainer ? modalContainer.animY : 0
|
||||
};
|
||||
const state = {
|
||||
"visible": shouldBeVisible || contentWindow.visible,
|
||||
"kind": "modal",
|
||||
"screenName": root._currentScreenName(),
|
||||
"phase": phase,
|
||||
"visible": presented,
|
||||
"presented": presented,
|
||||
"barSide": resolvedConnectedBarSide,
|
||||
"bodyRect": bodyRect,
|
||||
"animationOffset": animationOffset,
|
||||
"scale": 1,
|
||||
"opacity": Theme.connectedSurfaceColor.a,
|
||||
"bodyX": alignedX,
|
||||
"bodyY": alignedY,
|
||||
"bodyW": alignedWidth,
|
||||
"bodyH": alignedHeight,
|
||||
"animX": modalContainer ? modalContainer.animX : 0,
|
||||
"animY": modalContainer ? modalContainer.animY : 0,
|
||||
"animX": animationOffset.x,
|
||||
"animY": animationOffset.y,
|
||||
"omitStartConnector": false,
|
||||
"omitEndConnector": false
|
||||
"omitEndConnector": false,
|
||||
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
|
||||
};
|
||||
return modalChrome.publish(state);
|
||||
}
|
||||
@@ -222,22 +240,16 @@ Item {
|
||||
const focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen) {
|
||||
contentWindow.screen = focusedScreen;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.screen = focusedScreen;
|
||||
}
|
||||
|
||||
ModalManager.openModal(modalHandle);
|
||||
if (Theme.isDirectionalEffect || root.useBackground) {
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.visible = true;
|
||||
contentWindow.visible = true;
|
||||
}
|
||||
|
||||
Qt.callLater(() => {
|
||||
animationsEnabled = true;
|
||||
shouldBeVisible = true;
|
||||
if (!useSingleWindow && !clickCatcher.visible)
|
||||
clickCatcher.visible = true;
|
||||
if (!contentWindow.visible)
|
||||
contentWindow.visible = true;
|
||||
opened();
|
||||
@@ -264,8 +276,6 @@ Item {
|
||||
ModalManager.closeModal(modalHandle);
|
||||
closeTimer.stop();
|
||||
contentWindow.visible = false;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.visible = false;
|
||||
dialogClosed();
|
||||
Qt.callLater(() => animationsEnabled = true);
|
||||
}
|
||||
@@ -304,8 +314,6 @@ Item {
|
||||
const newScreen = CompositorService.getFocusedScreen();
|
||||
if (newScreen) {
|
||||
contentWindow.screen = newScreen;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.screen = newScreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,29 +325,12 @@ Item {
|
||||
if (shouldBeVisible)
|
||||
return;
|
||||
contentWindow.visible = false;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.visible = false;
|
||||
dialogClosed();
|
||||
}
|
||||
}
|
||||
|
||||
// shadowRenderPadding is zeroed when frame owns the chrome
|
||||
// Wayland then clips any content translating past
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowMotionPadding: {
|
||||
if (frameOwnsConnectedChrome)
|
||||
return 0;
|
||||
if (animationType === "slide")
|
||||
return 30;
|
||||
if (Theme.isDirectionalEffect)
|
||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
|
||||
if (Theme.isDepthEffect)
|
||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
|
||||
return Math.max(0, animationOffset);
|
||||
}
|
||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||
|
||||
@@ -349,7 +340,6 @@ Item {
|
||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||
}
|
||||
|
||||
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
|
||||
readonly property real _connectedAlignedX: {
|
||||
switch (resolvedConnectedBarSide) {
|
||||
case "top":
|
||||
@@ -412,57 +402,6 @@ Item {
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
PanelWindow {
|
||||
id: clickCatcher
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: Rectangle {
|
||||
x: root.alignedX
|
||||
y: root.alignedY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
}
|
||||
intersection: Intersection.Xor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
color: "black"
|
||||
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: contentWindow
|
||||
visible: false
|
||||
@@ -472,8 +411,8 @@ Item {
|
||||
targetWindow: contentWindow
|
||||
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
|
||||
readonly property real s: Math.min(1, modalContainer.scaleValue)
|
||||
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
||||
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||
blurX: connectedReveal.x + modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
|
||||
blurY: connectedReveal.y + modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
|
||||
blurWidth: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
|
||||
blurHeight: (root.shouldBeVisible && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
|
||||
blurRadius: root.effectiveCornerRadius
|
||||
@@ -487,36 +426,15 @@ Item {
|
||||
"error": true
|
||||
})
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
return customKeyboardFocus;
|
||||
if (!shouldHaveFocus)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (root.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
right: root.useSingleWindow
|
||||
bottom: root.useSingleWindow
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: actualMarginLeft
|
||||
top: actualMarginTop
|
||||
right: 0
|
||||
bottom: 0
|
||||
}
|
||||
|
||||
implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
|
||||
implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
return;
|
||||
@@ -528,7 +446,7 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
||||
z: -2
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
@@ -537,7 +455,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
color: "black"
|
||||
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -551,249 +469,256 @@ Item {
|
||||
}
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
||||
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
||||
|
||||
id: connectedReveal
|
||||
// Clip to final footprint while frame-owned chrome grows from the bar edge.
|
||||
x: root.alignedX
|
||||
y: root.alignedY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.useSingleWindow && root.shouldBeVisible
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onClicked: mouse.accepted = true
|
||||
z: -1
|
||||
}
|
||||
|
||||
readonly property bool slide: root.animationType === "slide"
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||
readonly property real customDistLeft: customAnchorX
|
||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||
readonly property real customDistTop: customAnchorY
|
||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
|
||||
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||
readonly property real offsetX: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "left":
|
||||
return -connectedEmergenceTravelX;
|
||||
case "right":
|
||||
return connectedEmergenceTravelX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return 15;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -directionalTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -depthTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return depthTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
readonly property real offsetY: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "top":
|
||||
return -connectedEmergenceTravelY;
|
||||
case "bottom":
|
||||
return connectedEmergenceTravelY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return -30;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -Math.max(directionalTravel * 0.65, 96);
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -directionalTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
// Default to sliding down from top when centered
|
||||
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -depthTravel * 0.75;
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -depthTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return depthTravel;
|
||||
return depthTravel * 0.45;
|
||||
default:
|
||||
return -depthTravel;
|
||||
}
|
||||
}
|
||||
return root.animationOffset;
|
||||
}
|
||||
|
||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||
|
||||
// openProgress: 0 = closed (at frozenMotionOffset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: root.shouldBeVisible ? 1 : 0
|
||||
Behavior on openProgress {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
||||
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
||||
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
||||
|
||||
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
||||
root._queueAnimSync()
|
||||
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
||||
root._queueAnimSync()
|
||||
clip: root.frameOwnsConnectedChrome
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
clip: false
|
||||
id: modalContainer
|
||||
x: Theme.snap(animX, root.dpr)
|
||||
y: Theme.snap(animY, root.dpr)
|
||||
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.shouldBeVisible
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onClicked: mouse.accepted = true
|
||||
z: -1
|
||||
}
|
||||
|
||||
readonly property bool slide: root.animationType === "slide"
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||
readonly property real customDistLeft: customAnchorX
|
||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||
readonly property real customDistTop: customAnchorY
|
||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||
readonly property real offsetX: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "left":
|
||||
return -connectedEmergenceTravelX;
|
||||
case "right":
|
||||
return connectedEmergenceTravelX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return 15;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -directionalTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -depthTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return depthTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
readonly property real offsetY: {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
switch (root.resolvedConnectedBarSide) {
|
||||
case "top":
|
||||
return -connectedEmergenceTravelY;
|
||||
case "bottom":
|
||||
return connectedEmergenceTravelY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return -30;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -Math.max(directionalTravel * 0.65, 96);
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -directionalTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -depthTravel * 0.75;
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -depthTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return depthTravel;
|
||||
return depthTravel * 0.45;
|
||||
default:
|
||||
return -depthTravel;
|
||||
}
|
||||
}
|
||||
return root.animationOffset;
|
||||
}
|
||||
|
||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: root.shouldBeVisible ? 1 : 0
|
||||
Behavior on openProgress {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real animX: root.frozenMotionOffsetX * (1 - morph.openProgress)
|
||||
readonly property real animY: root.frozenMotionOffsetY * (1 - morph.openProgress)
|
||||
readonly property real scaleValue: computedScaleCollapsed + (1.0 - computedScaleCollapsed) * morph.openProgress
|
||||
|
||||
onAnimXChanged: if (root.frameOwnsConnectedChrome)
|
||||
root._queueAnimSync()
|
||||
onAnimYChanged: if (root.frameOwnsConnectedChrome)
|
||||
root._queueAnimSync()
|
||||
|
||||
Item {
|
||||
id: animatedContent
|
||||
anchors.fill: parent
|
||||
id: contentContainer
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
clip: false
|
||||
|
||||
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||
|
||||
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||
scale: modalContainer.scaleValue
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on publishedOpacity {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
ElevationShadow {
|
||||
id: modalShadowLayer
|
||||
Item {
|
||||
id: animatedContent
|
||||
anchors.fill: parent
|
||||
level: root.shadowLevel
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: root.effectiveCornerRadius
|
||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.effectiveCornerRadius
|
||||
color: "transparent"
|
||||
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
||||
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
||||
z: 100
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: root.shouldBeVisible
|
||||
clip: false
|
||||
|
||||
Item {
|
||||
id: directContentWrapper
|
||||
property real publishedOpacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||
|
||||
opacity: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||
scale: modalContainer.scaleValue
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on publishedOpacity {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || Theme.isConnectedEffect)
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
ElevationShadow {
|
||||
id: modalShadowLayer
|
||||
anchors.fill: parent
|
||||
visible: root.directContent !== null
|
||||
focus: true
|
||||
level: root.shadowLevel
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: root.effectiveCornerRadius
|
||||
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
|
||||
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
|
||||
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
|
||||
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.effectiveCornerRadius
|
||||
color: "transparent"
|
||||
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
|
||||
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
|
||||
z: 100
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: root.shouldBeVisible
|
||||
clip: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.directContent) {
|
||||
root.directContent.parent = directContentWrapper;
|
||||
root.directContent.anchors.fill = directContentWrapper;
|
||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: directContentWrapper
|
||||
anchors.fill: parent
|
||||
visible: root.directContent !== null
|
||||
focus: true
|
||||
clip: false
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onDirectContentChanged() {
|
||||
Component.onCompleted: {
|
||||
if (root.directContent) {
|
||||
root.directContent.parent = directContentWrapper;
|
||||
root.directContent.anchors.fill = directContentWrapper;
|
||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onDirectContentChanged() {
|
||||
if (root.directContent) {
|
||||
root.directContent.parent = directContentWrapper;
|
||||
root.directContent.anchors.fill = directContentWrapper;
|
||||
Qt.callLater(() => root.directContent.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
||||
asynchronous: false
|
||||
focus: true
|
||||
clip: false
|
||||
visible: root.directContent === null
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
|
||||
asynchronous: false
|
||||
focus: true
|
||||
clip: false
|
||||
visible: root.directContent === null
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ Item {
|
||||
id: clickCatcher
|
||||
visible: false
|
||||
color: "transparent"
|
||||
updatesEnabled: false
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
@@ -259,15 +260,7 @@ Item {
|
||||
"error": true
|
||||
})
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (customKeyboardFocus !== null)
|
||||
return customKeyboardFocus;
|
||||
if (!shouldHaveFocus)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (root.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus)
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
@@ -13,11 +12,6 @@ DankModal {
|
||||
|
||||
layerNamespace: "dms:color-picker"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property string pickerTitle: I18n.tr("Choose Color")
|
||||
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
|
||||
property var onColorSelectedCallback: null
|
||||
|
||||
@@ -30,7 +30,6 @@ Item {
|
||||
property string _pendingMode: ""
|
||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
||||
|
||||
// Animation state — matches DankPopout/DankModal pattern
|
||||
property bool animationsEnabled: true
|
||||
property bool _motionActive: false
|
||||
property real _frozenMotionX: 0
|
||||
@@ -108,8 +107,6 @@ Item {
|
||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
||||
}
|
||||
|
||||
// frameEdgeInsetForSide is the full inset; do not add frameBarSize.
|
||||
// Positions the modal flush to the emerge side, centered on the cross axis.
|
||||
readonly property var _connectedModalPos: {
|
||||
const fallback = {
|
||||
"x": (screenWidth - modalWidth) / 2,
|
||||
@@ -175,8 +172,6 @@ Item {
|
||||
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
|
||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||
|
||||
// Shadow padding for the content window (render padding only, no motion padding).
|
||||
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
@@ -203,29 +198,11 @@ Item {
|
||||
}
|
||||
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
||||
|
||||
// For directional/depth: window extends from screen top (content slides within)
|
||||
// For standard: small window tightly around the modal + shadow padding
|
||||
readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect
|
||||
// Content window geometry
|
||||
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
|
||||
readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr))
|
||||
readonly property real _cwWidth: alignedWidth + shadowPad * 2
|
||||
readonly property real _cwHeight: {
|
||||
if (launcherArcExtenderActive)
|
||||
return _connectedChromeHeight;
|
||||
if (Theme.isDirectionalEffect && !Theme.isConnectedEffect)
|
||||
return screenHeight + shadowPad;
|
||||
if (Theme.isDepthEffect)
|
||||
return alignedY + alignedHeight + shadowPad;
|
||||
return alignedHeight + shadowPad * 2;
|
||||
}
|
||||
// Where the content container sits inside the content window
|
||||
readonly property real _ccX: shadowPad
|
||||
readonly property real _ccY: launcherArcExtenderActive ? 0 : (_needsExtendedWindow ? alignedY : shadowPad)
|
||||
readonly property real _ccX: _connectedChromeX
|
||||
readonly property real _ccY: _connectedChromeY
|
||||
|
||||
signal dialogClosed
|
||||
|
||||
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
||||
Timer {
|
||||
id: _syncTimer
|
||||
interval: 0
|
||||
@@ -242,6 +219,7 @@ Item {
|
||||
id: modalChrome
|
||||
modalHandle: root.modalHandle
|
||||
claimPrefix: "dms:launcher-v2"
|
||||
surfaceKind: "launcher"
|
||||
screenName: root._currentScreenName()
|
||||
enabled: root.frameOwnsConnectedChrome
|
||||
active: root.spotlightOpen
|
||||
@@ -252,17 +230,38 @@ Item {
|
||||
}
|
||||
|
||||
function _publishModalChromeState() {
|
||||
const presented = spotlightOpen || contentWindow.visible;
|
||||
const phase = !presented ? "hidden" : (isClosing ? "closing" : (!contentWindow.visible ? "opening" : "open"));
|
||||
const bodyRect = {
|
||||
"x": _connectedChromeX,
|
||||
"y": _connectedChromeY,
|
||||
"width": _connectedChromeWidth,
|
||||
"height": _connectedChromeHeight
|
||||
};
|
||||
const animationOffset = {
|
||||
"x": contentContainer ? contentContainer.animX : 0,
|
||||
"y": contentContainer ? contentContainer.animY : 0
|
||||
};
|
||||
const state = {
|
||||
"visible": spotlightOpen || contentWindow.visible,
|
||||
"kind": "launcher",
|
||||
"screenName": root._currentScreenName(),
|
||||
"phase": phase,
|
||||
"visible": presented,
|
||||
"presented": presented,
|
||||
"barSide": resolvedConnectedBarSide,
|
||||
"bodyRect": bodyRect,
|
||||
"animationOffset": animationOffset,
|
||||
"scale": 1,
|
||||
"opacity": Theme.connectedSurfaceColor.a,
|
||||
"bodyX": _connectedChromeX,
|
||||
"bodyY": _connectedChromeY,
|
||||
"bodyW": _connectedChromeWidth,
|
||||
"bodyH": _connectedChromeHeight,
|
||||
"animX": contentContainer ? contentContainer.animX : 0,
|
||||
"animY": contentContainer ? contentContainer.animY : 0,
|
||||
"animX": animationOffset.x,
|
||||
"animY": animationOffset.y,
|
||||
"omitStartConnector": false,
|
||||
"omitEndConnector": false
|
||||
"omitEndConnector": false,
|
||||
"dockRetractSide": root._dockBlocksEmergence ? resolvedConnectedBarSide : ""
|
||||
};
|
||||
return modalChrome.publish(state);
|
||||
}
|
||||
@@ -359,8 +358,6 @@ Item {
|
||||
return;
|
||||
contentVisible = true;
|
||||
spotlightContent.closeTransientUi?.();
|
||||
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
||||
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
||||
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = query;
|
||||
@@ -398,40 +395,29 @@ Item {
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
// Disable animations so the snap is instant
|
||||
animationsEnabled = false;
|
||||
|
||||
// Freeze the collapsed offsets (they depend on height which could change)
|
||||
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
||||
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen) {
|
||||
backgroundWindow.screen = focusedScreen;
|
||||
contentWindow.screen = focusedScreen;
|
||||
}
|
||||
|
||||
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
|
||||
_motionActive = false;
|
||||
|
||||
// Make windows visible but do NOT request keyboard focus yet
|
||||
ModalManager.openModal(modalHandle);
|
||||
spotlightOpen = true;
|
||||
backgroundWindow.visible = true;
|
||||
contentWindow.visible = true;
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
// Load content and initialize (but no forceActiveFocus — that's deferred)
|
||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||
|
||||
// Frame 1: enable animations and trigger enter motion
|
||||
// Defer focus until after enter motion starts (avoids compositor IPC stalls).
|
||||
Qt.callLater(() => {
|
||||
root.animationsEnabled = true;
|
||||
root._motionActive = true;
|
||||
|
||||
// Frame 2: request keyboard focus + activate search field
|
||||
// Double-deferred to avoid compositor IPC competing with animation frames
|
||||
Qt.callLater(() => {
|
||||
root.keyboardActive = true;
|
||||
if (root.spotlightContent && root.spotlightContent.searchField)
|
||||
@@ -454,16 +440,13 @@ Item {
|
||||
spotlightContent?.closeTransientUi?.();
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||
if (!Theme.isDirectionalEffect)
|
||||
contentVisible = false;
|
||||
|
||||
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
||||
_motionActive = false;
|
||||
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(modalHandle);
|
||||
closeCleanupTimer.start();
|
||||
}
|
||||
@@ -500,7 +483,6 @@ Item {
|
||||
isClosing = false;
|
||||
contentVisible = false;
|
||||
contentWindow.visible = false;
|
||||
backgroundWindow.visible = false;
|
||||
if (root.unloadContentOnClose)
|
||||
launcherContentLoader.active = false;
|
||||
dialogClosed();
|
||||
@@ -519,7 +501,7 @@ Item {
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [contentWindow]
|
||||
active: false
|
||||
active: root.useHyprlandFocusGrab && root.spotlightOpen
|
||||
|
||||
onCleared: {
|
||||
if (spotlightOpen) {
|
||||
@@ -569,7 +551,6 @@ Item {
|
||||
|
||||
root._releaseModalChrome();
|
||||
root._windowEnabled = false;
|
||||
backgroundWindow.screen = newScreen;
|
||||
contentWindow.screen = newScreen;
|
||||
Qt.callLater(() => {
|
||||
root._windowEnabled = true;
|
||||
@@ -577,73 +558,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: backgroundWindow
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
readonly property real _topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||
readonly property real _bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||
readonly property real _leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||
readonly property real _rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight:bg"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
WlrLayershell.margins {
|
||||
top: backgroundWindow._topMargin
|
||||
bottom: backgroundWindow._bottomMargin
|
||||
left: backgroundWindow._leftMargin
|
||||
right: backgroundWindow._rightMargin
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
||||
|
||||
Region {
|
||||
item: bgContentHole
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bgFullScreenMask
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bgContentHole
|
||||
visible: false
|
||||
x: root._cwMarginLeft + contentContainer.x - backgroundWindow._leftMargin
|
||||
y: root._cwMarginTop + contentContainer.y - backgroundWindow._topMargin
|
||||
width: root.alignedWidth
|
||||
height: root.contentSurfaceHeight
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundDarken
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: 0
|
||||
visible: false
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: contentWindow
|
||||
visible: false
|
||||
@@ -663,23 +577,31 @@ Item {
|
||||
WlrLayershell.namespace: "dms:spotlight"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root._cwMarginLeft
|
||||
top: root._cwMarginTop
|
||||
}
|
||||
|
||||
implicitWidth: root._cwWidth
|
||||
implicitHeight: root._cwHeight
|
||||
|
||||
mask: Region {
|
||||
item: contentInputMask
|
||||
item: (root.spotlightOpen || root.isClosing) ? dismissArea : contentInputMask
|
||||
|
||||
Region {
|
||||
item: (root.spotlightOpen || root.isClosing) ? contentInputMask : null
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dismissArea
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||
anchors.bottomMargin: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||
anchors.leftMargin: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||
anchors.rightMargin: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -691,16 +613,31 @@ Item {
|
||||
height: root.contentSurfaceHeight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: dismissArea
|
||||
enabled: root.spotlightOpen
|
||||
z: -2
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
|
||||
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
|
||||
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
|
||||
x: root._ccX
|
||||
y: root._ccY
|
||||
width: root.alignedWidth
|
||||
height: root.contentSurfaceHeight
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.spotlightOpen
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onClicked: mouse.accepted = true
|
||||
z: -1
|
||||
}
|
||||
|
||||
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
||||
readonly property bool dockTop: dockEdge === 0
|
||||
readonly property bool dockBottom: dockEdge === 1
|
||||
@@ -755,7 +692,6 @@ Item {
|
||||
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
||||
}
|
||||
|
||||
// openProgress: 0 = closed (at frozenMotion, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: root._motionActive ? 1 : 0
|
||||
@@ -814,7 +750,6 @@ Item {
|
||||
width: contentContainer.width
|
||||
height: contentContainer.height
|
||||
|
||||
// Shadow mirrors contentWrapper position/scale/opacity
|
||||
ElevationShadow {
|
||||
id: launcherShadowLayer
|
||||
width: parent.width
|
||||
@@ -832,7 +767,6 @@ Item {
|
||||
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
// contentWrapper moves inside static contentContainer — DankPopout pattern
|
||||
Item {
|
||||
id: contentWrapper
|
||||
width: parent.width
|
||||
|
||||
@@ -84,14 +84,14 @@ Item {
|
||||
readonly property real alignedX: Theme.snap(modalX, dpr)
|
||||
readonly property real alignedY: Theme.snap(modalY, dpr)
|
||||
|
||||
// Extra headroom above the window for the slide-in animation
|
||||
// Extra headroom above the content for the slide-in animation
|
||||
readonly property real _animHeadroom: 16
|
||||
readonly property real windowX: Math.max(0, Theme.snap(alignedX - shadowPad, dpr))
|
||||
readonly property real windowY: Math.max(0, Theme.snap(alignedY - shadowPad - _animHeadroom, dpr))
|
||||
readonly property real contentX: Theme.snap(alignedX - windowX, dpr)
|
||||
readonly property real contentY: Theme.snap(alignedY - windowY, dpr)
|
||||
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
||||
readonly property real _animatedContentH: Theme.snap(_contentImplicitH, dpr)
|
||||
readonly property real windowWidth: alignedWidth + contentX + shadowPad
|
||||
readonly property real windowHeight: _animatedContentH + contentY + shadowPad + _animHeadroom
|
||||
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
@@ -114,6 +114,7 @@ Item {
|
||||
}
|
||||
}
|
||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
||||
|
||||
signal dialogClosed
|
||||
|
||||
@@ -164,8 +165,6 @@ Item {
|
||||
openedFromOverview = false;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(modalHandle);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||
}
|
||||
|
||||
@@ -201,7 +200,6 @@ Item {
|
||||
contentVisible = false;
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(modalHandle);
|
||||
closeCleanupTimer.start();
|
||||
}
|
||||
@@ -231,7 +229,7 @@ Item {
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [launcherWindow]
|
||||
active: false
|
||||
active: root.useHyprlandFocusGrab && root.keyboardActive
|
||||
onCleared: {
|
||||
if (spotlightOpen)
|
||||
hide();
|
||||
@@ -270,8 +268,9 @@ Item {
|
||||
PanelWindow {
|
||||
id: clickCatcher
|
||||
screen: launcherWindow.screen
|
||||
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
||||
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
||||
color: "transparent"
|
||||
updatesEnabled: false
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
@@ -337,24 +336,24 @@ Item {
|
||||
WlrLayershell.namespace: "dms:spotlight"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: root.useBackgroundDarken
|
||||
bottom: root.useBackgroundDarken
|
||||
right: root.useSingleWindow
|
||||
bottom: root.useSingleWindow
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root.useBackgroundDarken ? 0 : root.windowX
|
||||
top: root.useBackgroundDarken ? 0 : root.windowY
|
||||
left: root.useSingleWindow ? 0 : root.windowX
|
||||
top: root.useSingleWindow ? 0 : root.windowY
|
||||
right: 0
|
||||
bottom: 0
|
||||
}
|
||||
|
||||
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
||||
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
||||
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
||||
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
||||
|
||||
mask: Region {
|
||||
item: inputMask
|
||||
@@ -364,15 +363,15 @@ Item {
|
||||
id: inputMask
|
||||
visible: false
|
||||
color: "transparent"
|
||||
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
||||
y: root.useBackgroundDarken ? 0 : modalContainer.y + modalContainer.slideOffset
|
||||
width: root.useBackgroundDarken ? launcherWindow.width : root.alignedWidth
|
||||
height: root.useBackgroundDarken ? launcherWindow.height : root._contentImplicitH
|
||||
x: root.useSingleWindow ? 0 : modalContainer.x
|
||||
y: root.useSingleWindow ? 0 : modalContainer.y + modalContainer.slideOffset
|
||||
width: root.useSingleWindow ? launcherWindow.width : root.alignedWidth
|
||||
height: root.useSingleWindow ? launcherWindow.height : root._contentImplicitH
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.useBackgroundDarken && spotlightOpen
|
||||
enabled: root.useSingleWindow && spotlightOpen
|
||||
z: -2
|
||||
onClicked: root.hide()
|
||||
}
|
||||
@@ -396,13 +395,23 @@ Item {
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
||||
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
||||
x: root.useSingleWindow ? root.alignedX : root.contentX
|
||||
y: root.useSingleWindow ? root.alignedY : root.contentY
|
||||
width: root.alignedWidth
|
||||
height: root._animatedContentH
|
||||
visible: _renderActive
|
||||
z: 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onClicked: mouse.accepted = true
|
||||
z: -1
|
||||
}
|
||||
|
||||
property bool _renderActive: contentVisible
|
||||
property real slideOffset: contentVisible ? 0 : -root._animHeadroom
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ Item {
|
||||
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
readonly property bool useBackgroundDarken: !SettingsData.frameEnabled && SettingsData.modalDarkenBackground
|
||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken
|
||||
readonly property bool usesOverlayLayer: useBackgroundDarken || SettingsData.launcherUseOverlayLayer || triggerUsesOverlayLayer
|
||||
readonly property var effectiveLauncherLayer: LayerShell.fromEnv("DMS_MODAL_LAYER", root.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
@@ -172,8 +173,6 @@ Item {
|
||||
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(modalHandle);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||
}
|
||||
@@ -211,7 +210,6 @@ Item {
|
||||
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(modalHandle);
|
||||
|
||||
closeCleanupTimer.start();
|
||||
@@ -262,7 +260,7 @@ Item {
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [launcherWindow]
|
||||
active: false
|
||||
active: root.useHyprlandFocusGrab && root.keyboardActive
|
||||
|
||||
onCleared: {
|
||||
if (spotlightOpen) {
|
||||
@@ -306,8 +304,9 @@ Item {
|
||||
PanelWindow {
|
||||
id: clickCatcher
|
||||
screen: launcherWindow.screen
|
||||
visible: (spotlightOpen || isClosing) && !root.useBackgroundDarken
|
||||
visible: (spotlightOpen || isClosing) && !root.useSingleWindow
|
||||
color: "transparent"
|
||||
updatesEnabled: false
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight:clickcatcher"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
@@ -373,24 +372,24 @@ Item {
|
||||
WlrLayershell.namespace: "dms:spotlight"
|
||||
WlrLayershell.layer: root.effectiveLauncherLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None)
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null)
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: root.useBackgroundDarken
|
||||
bottom: root.useBackgroundDarken
|
||||
right: root.useSingleWindow
|
||||
bottom: root.useSingleWindow
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root.useBackgroundDarken ? 0 : root.windowX
|
||||
top: root.useBackgroundDarken ? 0 : root.windowY
|
||||
left: root.useSingleWindow ? 0 : root.windowX
|
||||
top: root.useSingleWindow ? 0 : root.windowY
|
||||
right: 0
|
||||
bottom: 0
|
||||
}
|
||||
|
||||
implicitWidth: root.useBackgroundDarken ? 0 : root.windowWidth
|
||||
implicitHeight: root.useBackgroundDarken ? 0 : root.windowHeight
|
||||
implicitWidth: root.useSingleWindow ? 0 : root.windowWidth
|
||||
implicitHeight: root.useSingleWindow ? 0 : root.windowHeight
|
||||
|
||||
mask: Region {
|
||||
item: launcherInputMask
|
||||
@@ -400,15 +399,15 @@ Item {
|
||||
id: launcherInputMask
|
||||
visible: false
|
||||
color: "transparent"
|
||||
x: root.useBackgroundDarken ? 0 : modalContainer.x
|
||||
y: root.useBackgroundDarken ? 0 : modalContainer.y
|
||||
width: root.useBackgroundDarken ? launcherWindow.width : modalContainer.width
|
||||
height: root.useBackgroundDarken ? launcherWindow.height : modalContainer.height
|
||||
x: root.useSingleWindow ? 0 : modalContainer.x
|
||||
y: root.useSingleWindow ? 0 : modalContainer.y
|
||||
width: root.useSingleWindow ? launcherWindow.width : modalContainer.width
|
||||
height: root.useSingleWindow ? launcherWindow.height : modalContainer.height
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.useBackgroundDarken && spotlightOpen
|
||||
enabled: root.useSingleWindow && spotlightOpen
|
||||
z: -2
|
||||
onClicked: root.hide()
|
||||
}
|
||||
@@ -432,13 +431,23 @@ Item {
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.useBackgroundDarken ? root.alignedX : root.contentX
|
||||
y: root.useBackgroundDarken ? root.alignedY : root.contentY
|
||||
x: root.useSingleWindow ? root.alignedX : root.contentX
|
||||
y: root.useSingleWindow ? root.alignedY : root.contentY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
visible: _renderActive
|
||||
z: 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onPressed: mouse.accepted = true
|
||||
onClicked: mouse.accepted = true
|
||||
z: -1
|
||||
}
|
||||
|
||||
property bool _renderActive: contentVisible
|
||||
property real publishedScale: contentVisible ? 1 : 0.96
|
||||
property real publishedOpacity: contentVisible ? 1 : 0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
@@ -29,11 +28,6 @@ DankModal {
|
||||
KeybindsService.loadCheatsheet();
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
function scrollDown() {
|
||||
if (!root.activeFlickable)
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
@@ -45,12 +44,6 @@ DankModal {
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [muxModal.contentWindow]
|
||||
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible) {
|
||||
hide();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
@@ -11,11 +10,6 @@ DankModal {
|
||||
|
||||
layerNamespace: "dms:notification-center-modal"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [notificationModal.contentWindow]
|
||||
active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property bool notificationModalOpen: false
|
||||
property var notificationListRef: null
|
||||
property var historyListRef: null
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
@@ -13,11 +12,6 @@ DankModal {
|
||||
layerNamespace: "dms:power-menu"
|
||||
keepPopoutsOpen: true
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root.contentWindow]
|
||||
active: root.useHyprlandFocusGrab && root.shouldHaveFocus
|
||||
}
|
||||
|
||||
property int selectedIndex: 0
|
||||
property int selectedRow: 0
|
||||
property int selectedCol: 0
|
||||
|
||||
@@ -109,15 +109,7 @@ DankPopout {
|
||||
close();
|
||||
}
|
||||
|
||||
customKeyboardFocus: {
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (anyModalOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
|
||||
|
||||
onBackgroundClicked: close()
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Item {
|
||||
// M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support
|
||||
readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
|
||||
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 globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
||||
readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
|
||||
@@ -207,7 +207,6 @@ Item {
|
||||
shadowOffsetX: root.shadowOffsetX
|
||||
shadowOffsetY: root.shadowOffsetY
|
||||
shadowColor: root.shadowColor
|
||||
blurMax: Theme.elevationBlurMax
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -9,6 +9,8 @@ PanelWindow {
|
||||
id: barWindow
|
||||
readonly property var log: Log.scoped("DankBarWindow")
|
||||
|
||||
Component.onDestruction: KeyboardFocus.unregisterBarWindow(barWindow)
|
||||
|
||||
required property var rootWindow
|
||||
required property var barConfig
|
||||
property var modelData: item
|
||||
@@ -18,6 +20,8 @@ PanelWindow {
|
||||
property var centerWidgetsModel
|
||||
property var rightWidgetsModel
|
||||
|
||||
readonly property bool barRevealed: inputMask.showing
|
||||
|
||||
property var controlCenterButtonRef: null
|
||||
property var clockButtonRef: null
|
||||
property var systemUpdateButtonRef: null
|
||||
@@ -555,6 +559,7 @@ PanelWindow {
|
||||
color: "transparent"
|
||||
|
||||
Component.onCompleted: {
|
||||
KeyboardFocus.registerBarWindow(barWindow);
|
||||
updateGpuTempConfig();
|
||||
_updateBackgroundAlpha();
|
||||
_updateHasMaximizedToplevel();
|
||||
@@ -956,8 +961,13 @@ PanelWindow {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: {
|
||||
const screenName = barWindow.screen?.name;
|
||||
if (screenName && PopoutManager.currentPopoutsByScreen[screenName])
|
||||
if (!screenName)
|
||||
return;
|
||||
if (PopoutManager.currentPopoutsByScreen[screenName])
|
||||
PopoutManager.closeAllPopouts();
|
||||
if (ModalManager.currentModalsByScreen[screenName])
|
||||
ModalManager.closeAllModalsExcept(null);
|
||||
TrayMenuManager.closeAllMenus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,7 @@ DankPopout {
|
||||
|
||||
backgroundInteractive: !anyModalOpen
|
||||
|
||||
customKeyboardFocus: {
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (anyModalOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null
|
||||
|
||||
Connections {
|
||||
target: SystemUpdateService
|
||||
|
||||
@@ -980,21 +980,13 @@ BasePill {
|
||||
screen: root.parentScreen
|
||||
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (PopoutManager.screenshotActive)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!root.menuOpen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.menuOpen, null)
|
||||
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
||||
color: "transparent"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [overflowMenu]
|
||||
active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen
|
||||
windows: [overflowMenu].concat(KeyboardFocus.barWindows)
|
||||
active: root.useOverflowPopup && KeyboardFocus.wantsGrab(root.menuOpen, null)
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -1051,32 +1043,21 @@ BasePill {
|
||||
"leftBar": 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: {
|
||||
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarX, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarY, adjacentTopBar);
|
||||
}
|
||||
|
||||
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);
|
||||
DismissZone {
|
||||
id: _overflowDismissZone
|
||||
barPosition: overflowMenu.barPosition
|
||||
barX: overflowMenu.barX
|
||||
barY: overflowMenu.barY
|
||||
barWidth: overflowMenu.barWidth
|
||||
barHeight: overflowMenu.barHeight
|
||||
screenWidth: overflowMenu.width
|
||||
screenHeight: overflowMenu.height
|
||||
adjacentBarInfo: overflowMenu.adjacentBarInfo
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
@@ -1237,13 +1218,7 @@ BasePill {
|
||||
fallbackOffset: 6
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
targetRadius: Theme.cornerRadius
|
||||
sourceRect.antialiasing: true
|
||||
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
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -1450,20 +1425,12 @@ BasePill {
|
||||
screen: menuRoot.parentScreen
|
||||
WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (PopoutManager.screenshotActive)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!menuRoot.showMenu)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (CompositorService.useHyprlandFocusGrab)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(menuRoot.showMenu, null)
|
||||
color: "transparent"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [menuWindow]
|
||||
active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu
|
||||
windows: [menuWindow].concat(KeyboardFocus.barWindows)
|
||||
active: KeyboardFocus.wantsGrab(menuRoot.showMenu, null)
|
||||
}
|
||||
|
||||
anchors {
|
||||
@@ -1502,32 +1469,21 @@ BasePill {
|
||||
"leftBar": 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: {
|
||||
const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0;
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarX, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0;
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarY, adjacentTopBar);
|
||||
}
|
||||
|
||||
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);
|
||||
DismissZone {
|
||||
id: _menuDismissZone
|
||||
barPosition: menuWindow.barPosition
|
||||
barX: menuWindow.barX
|
||||
barY: menuWindow.barY
|
||||
barWidth: menuWindow.barWidth
|
||||
barHeight: menuWindow.barHeight
|
||||
screenWidth: menuWindow.width
|
||||
screenHeight: menuWindow.height
|
||||
adjacentBarInfo: menuWindow.adjacentBarInfo
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
@@ -1689,11 +1645,7 @@ BasePill {
|
||||
fallbackOffset: 6
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
targetRadius: Theme.cornerRadius
|
||||
sourceRect.antialiasing: true
|
||||
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
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -130,7 +130,7 @@ Item {
|
||||
borderColor: volumePanel.border.color
|
||||
borderWidth: volumePanel.border.width
|
||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
||||
shadowEnabled: Theme.elevationEnabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -272,7 +272,7 @@ Item {
|
||||
borderColor: audioDevicesPanel.border.color
|
||||
borderWidth: audioDevicesPanel.border.width
|
||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
||||
shadowEnabled: Theme.elevationEnabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -444,7 +444,7 @@ Item {
|
||||
borderColor: playersPanel.border.color
|
||||
borderWidth: playersPanel.border.width
|
||||
shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25
|
||||
shadowEnabled: Theme.elevationEnabled && !BlurService.enabled
|
||||
shadowEnabled: Theme.elevationEnabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -186,13 +186,36 @@ Variants {
|
||||
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, {
|
||||
"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,
|
||||
"bodyX": dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x,
|
||||
"bodyY": dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y,
|
||||
"bodyW": dock.hasApps ? dockBackground.width : 0,
|
||||
"bodyH": dock.hasApps ? dockBackground.height : 0,
|
||||
"bodyRect": {
|
||||
"x": bodyX,
|
||||
"y": bodyY,
|
||||
"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,
|
||||
"slideY": dockSlide.y
|
||||
});
|
||||
@@ -724,16 +747,36 @@ Variants {
|
||||
onHeightChanged: dock._syncDockChromeState()
|
||||
}
|
||||
|
||||
ConnectedShape {
|
||||
Item {
|
||||
id: dockConnectedChrome
|
||||
visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive
|
||||
barSide: dock.connectedBarSide
|
||||
bodyWidth: dockBackground.width
|
||||
bodyHeight: dockBackground.height
|
||||
connectorRadius: Theme.connectedCornerRadius
|
||||
surfaceRadius: dock.surfaceRadius
|
||||
fillColor: dock.surfaceColor
|
||||
x: dockBackground.x - bodyX
|
||||
y: dockBackground.y - bodyY
|
||||
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
||||
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
||||
readonly property real bodyRadius: dock.surfaceRadius
|
||||
readonly property bool barTop: dock.connectedBarSide === "top"
|
||||
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
||||
readonly property bool barLeft: dock.connectedBarSide === "left"
|
||||
readonly property bool barRight: dock.connectedBarSide === "right"
|
||||
|
||||
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 {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
|
||||
// Frame perimeter ring with rounded cutout (SDF).
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -16,39 +16,14 @@ Item {
|
||||
required property real cutoutRadius
|
||||
property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
|
||||
|
||||
Rectangle {
|
||||
id: borderRect
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
// Bake frameOpacity into the color alpha rather than using the `opacity` property
|
||||
color: root.borderColor
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/frame_arc.frag.qsb")
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskSource: cutoutMask
|
||||
maskEnabled: true
|
||||
maskInverted: true
|
||||
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
|
||||
}
|
||||
property real widthPx: width
|
||||
property real heightPx: height
|
||||
property real cutoutRadius: root.cutoutRadius
|
||||
property vector4d cutout: Qt.vector4d(root.cutoutLeftInset, root.cutoutTopInset, root.width - root.cutoutRightInset, root.height - root.cutoutBottomInset)
|
||||
property vector4d surfaceColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ Rectangle {
|
||||
height: baseCardHeight + contentItem.extraHeight
|
||||
radius: Theme.cornerRadius
|
||||
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 {
|
||||
id: shadowLayer
|
||||
|
||||
@@ -47,7 +47,7 @@ Rectangle {
|
||||
readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
|
||||
radius: connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||
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 real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4
|
||||
readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0
|
||||
|
||||
@@ -641,21 +641,15 @@ PanelWindow {
|
||||
shadowOffsetY: content.shadowOffsetY
|
||||
shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
|
||||
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
|
||||
sourceRect.x: content.shadowRenderPadding + content.cardInset
|
||||
sourceRect.y: content.shadowRenderPadding + content.cardInset
|
||||
sourceRect.width: Math.max(0, content.width - (content.cardInset * 2))
|
||||
sourceRect.height: Math.max(0, content.height - (content.cardInset * 2))
|
||||
sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||
sourceRect.color: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface
|
||||
sourceRect.antialiasing: true
|
||||
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
|
||||
sourceX: content.shadowRenderPadding + content.cardInset
|
||||
sourceY: content.shadowRenderPadding + content.cardInset
|
||||
sourceWidth: Math.max(0, content.width - (content.cardInset * 2))
|
||||
sourceHeight: Math.max(0, content.height - (content.cardInset * 2))
|
||||
targetRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||
targetColor: 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)
|
||||
borderWidth: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
|
||||
}
|
||||
|
||||
// Keep critical accent outside shadow rendering so connected mode still shows it.
|
||||
|
||||
@@ -513,13 +513,30 @@ QtObject {
|
||||
ConnectedModeState.clearNotificationState(screenName);
|
||||
return;
|
||||
}
|
||||
const bodyRect = {
|
||||
x: minX,
|
||||
y: minY,
|
||||
width: maxXEnd - minX,
|
||||
height: maxYEnd - minY
|
||||
};
|
||||
ConnectedModeState.setNotificationState(screenName, {
|
||||
kind: "notification",
|
||||
screenName: screenName,
|
||||
phase: "open",
|
||||
visible: true,
|
||||
presented: true,
|
||||
barSide: notifBarSide,
|
||||
bodyRect: bodyRect,
|
||||
animationOffset: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
scale: 1,
|
||||
opacity: Theme.connectedSurfaceColor.a,
|
||||
bodyX: minX,
|
||||
bodyY: minY,
|
||||
bodyW: maxXEnd - minX,
|
||||
bodyH: maxYEnd - minY,
|
||||
bodyW: bodyRect.width,
|
||||
bodyH: bodyRect.height,
|
||||
omitStartConnector: _notificationOmitStartConnector(),
|
||||
omitEndConnector: _notificationOmitEndConnector()
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ Item {
|
||||
property bool conditionVisible: true
|
||||
property bool _visibilityOverride: false
|
||||
property bool _visibilityOverrideValue: true
|
||||
readonly property bool _barRevealed: blurBarWindow?.barRevealed ?? true
|
||||
|
||||
readonly property bool effectiveVisible: {
|
||||
if (_visibilityOverride)
|
||||
@@ -122,6 +123,11 @@ Item {
|
||||
conditionVisible = true;
|
||||
}
|
||||
|
||||
on_BarRevealedChanged: {
|
||||
if (_barRevealed && visibilityCommand && !_visibilityOverride)
|
||||
checkVisibility();
|
||||
}
|
||||
|
||||
onVisibilityIntervalChanged: {
|
||||
if (visibilityInterval > 0 && visibilityCommand) {
|
||||
visibilityTimer.restart();
|
||||
@@ -134,7 +140,7 @@ Item {
|
||||
id: visibilityTimer
|
||||
interval: root.visibilityInterval * 1000
|
||||
repeat: true
|
||||
running: root.visibilityInterval > 0 && root.visibilityCommand !== ""
|
||||
running: root.visibilityInterval > 0 && root.visibilityCommand !== "" && root._barRevealed && !root._visibilityOverride
|
||||
onTriggered: root.checkVisibility()
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,8 +289,6 @@ PanelWindow {
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 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 {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -50,6 +51,20 @@ Item {
|
||||
readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader
|
||||
readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader
|
||||
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 {
|
||||
id: _fallbackContentLoader
|
||||
@@ -127,8 +142,6 @@ Item {
|
||||
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() {
|
||||
_resolveBackendForScreen(screen);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../Common/ConnectorGeometry.js" as ConnectorGeometry
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -18,6 +17,7 @@ Item {
|
||||
property Component overlayContent: null
|
||||
property alias overlayLoader: overlayLoader
|
||||
readonly property alias backgroundWindow: contentWindow
|
||||
readonly property alias contentWindow: contentWindow
|
||||
property real popupWidth: 400
|
||||
property real popupHeight: 300
|
||||
property real triggerX: 0
|
||||
@@ -38,8 +38,6 @@ Item {
|
||||
property bool fullHeightSurface: false
|
||||
property bool _primeContent: false
|
||||
property bool _resizeActive: false
|
||||
property string _chromeClaimId: ""
|
||||
property int _connectedChromeSerial: 0
|
||||
property real _chromeAnimTravelX: 1
|
||||
property real _chromeAnimTravelY: 1
|
||||
property bool _fullSyncQueued: false
|
||||
@@ -55,7 +53,6 @@ Item {
|
||||
"rightBar": 0
|
||||
})
|
||||
property var screen: null
|
||||
// Connected resize uses one full-screen surface; body-sized regions are masks.
|
||||
readonly property bool useBackgroundWindow: false
|
||||
readonly property var effectivePopoutLayer: LayerShell.fromEnv("DMS_POPOUT_LAYER", root.triggerUsesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top, {
|
||||
"allow": ["top", "overlay"],
|
||||
@@ -93,13 +90,49 @@ Item {
|
||||
signal popoutClosed
|
||||
signal backgroundClicked
|
||||
|
||||
// Coalesce per-channel dirty bits; one ConnectedModeState write per tick.
|
||||
Timer {
|
||||
id: _syncTimer
|
||||
interval: 0
|
||||
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 bool isClosing: false
|
||||
|
||||
@@ -169,11 +202,6 @@ Item {
|
||||
setBarContext(pos, bottomGap);
|
||||
}
|
||||
|
||||
function _nextChromeClaimId() {
|
||||
_connectedChromeSerial += 1;
|
||||
return layerNamespace + ":" + _connectedChromeSerial + ":" + (new Date()).getTime();
|
||||
}
|
||||
|
||||
function _captureChromeAnimTravel() {
|
||||
_chromeAnimTravelX = Math.max(1, Math.abs(contentContainer.offsetX));
|
||||
_chromeAnimTravelY = Math.max(1, Math.abs(contentContainer.offsetY));
|
||||
@@ -203,15 +231,35 @@ Item {
|
||||
|
||||
function _connectedChromeState(visibleOverride) {
|
||||
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 {
|
||||
"kind": "popout",
|
||||
"screenName": root.screen ? root.screen.name : "",
|
||||
"phase": phase,
|
||||
"visible": visible,
|
||||
"presented": presented,
|
||||
"barSide": contentContainer.connectedBarSide,
|
||||
"bodyRect": bodyRect,
|
||||
"animationOffset": animationOffset,
|
||||
"scale": 1,
|
||||
"opacity": Theme.connectedSurfaceColor.a,
|
||||
"bodyX": root.alignedX,
|
||||
"bodyY": root.renderedAlignedY,
|
||||
"bodyW": root.alignedWidth,
|
||||
"bodyH": root.renderedAlignedHeight,
|
||||
"animX": _connectedChromeAnimX(),
|
||||
"animY": _connectedChromeAnimY(),
|
||||
"animX": animationOffset.x,
|
||||
"animY": animationOffset.y,
|
||||
"screen": root.screen ? root.screen.name : "",
|
||||
"omitStartConnector": root._closeGapOmitStartConnector(),
|
||||
"omitEndConnector": root._closeGapOmitEndConnector()
|
||||
@@ -219,34 +267,18 @@ Item {
|
||||
}
|
||||
|
||||
function _publishConnectedChromeState(forceClaim, visibleOverride) {
|
||||
if (!root.frameOwnsConnectedChrome || !root.screen || !_chromeClaimId)
|
||||
if (!root.frameOwnsConnectedChrome || !root.screen)
|
||||
return false;
|
||||
|
||||
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);
|
||||
return chromeLease.publish(_connectedChromeState(visibleOverride), !!forceClaim);
|
||||
}
|
||||
|
||||
function _releaseConnectedChromeState() {
|
||||
if (_chromeClaimId)
|
||||
ConnectedModeState.releasePopout(_chromeClaimId);
|
||||
_chromeClaimId = "";
|
||||
chromeLease.release();
|
||||
}
|
||||
|
||||
// ─── Exposed animation state for ConnectedModeState ────────────────────
|
||||
readonly property real contentAnimX: contentContainer.animX
|
||||
readonly property real contentAnimY: contentContainer.animY
|
||||
|
||||
// ─── ConnectedModeState sync ────────────────────────────────────────────
|
||||
function _syncPopoutChromeState() {
|
||||
if (!root.frameOwnsConnectedChrome) {
|
||||
_releaseConnectedChromeState();
|
||||
@@ -258,16 +290,11 @@ Item {
|
||||
}
|
||||
if (!contentWindow.visible && !shouldBeVisible)
|
||||
return;
|
||||
if (!_chromeClaimId) {
|
||||
if (!PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
|
||||
return;
|
||||
_chromeClaimId = _nextChromeClaimId();
|
||||
}
|
||||
_publishConnectedChromeState(!ConnectedModeState.hasPopoutOwner(_chromeClaimId));
|
||||
_publishConnectedChromeState(false);
|
||||
}
|
||||
|
||||
function _syncPopoutAnim(axis) {
|
||||
if (!root.frameOwnsConnectedChrome || !_chromeClaimId)
|
||||
if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
|
||||
return;
|
||||
if (!contentWindow.visible && !shouldBeVisible)
|
||||
return;
|
||||
@@ -276,25 +303,15 @@ Item {
|
||||
const syncY = axis === "y" && (barSide === "top" || barSide === "bottom");
|
||||
if (!syncX && !syncY)
|
||||
return;
|
||||
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
|
||||
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
|
||||
_queueFullSync();
|
||||
return;
|
||||
}
|
||||
ConnectedModeState.setPopoutAnim(_chromeClaimId, syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
|
||||
chromeLease.updateAnim(syncX ? _connectedChromeAnimX() : undefined, syncY ? _connectedChromeAnimY() : undefined);
|
||||
}
|
||||
|
||||
function _syncPopoutBody() {
|
||||
if (!root.frameOwnsConnectedChrome || !_chromeClaimId)
|
||||
if (!root.frameOwnsConnectedChrome || !chromeLease.claimId)
|
||||
return;
|
||||
if (!contentWindow.visible && !shouldBeVisible)
|
||||
return;
|
||||
if (!ConnectedModeState.hasPopoutOwner(_chromeClaimId)) {
|
||||
if (root.screen && PopoutManager.isCurrentPopout(popoutHandle, root.screen.name))
|
||||
_queueFullSync();
|
||||
return;
|
||||
}
|
||||
ConnectedModeState.setPopoutBody(_chromeClaimId, root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
|
||||
chromeLease.updateBody(root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
|
||||
}
|
||||
|
||||
property bool _animSyncQueued: false
|
||||
@@ -345,13 +362,10 @@ Item {
|
||||
Connections {
|
||||
target: contentWindow
|
||||
function onVisibleChanged() {
|
||||
if (contentWindow.visible) {
|
||||
if (!root._chromeClaimId)
|
||||
root._chromeClaimId = root._nextChromeClaimId();
|
||||
if (contentWindow.visible)
|
||||
root._publishConnectedChromeState(true);
|
||||
} else {
|
||||
else
|
||||
root._releaseConnectedChromeState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,11 +373,8 @@ Item {
|
||||
target: SettingsData
|
||||
function onConnectedFrameModeActiveChanged() {
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name)) {
|
||||
if (!root._chromeClaimId)
|
||||
root._chromeClaimId = root._nextChromeClaimId();
|
||||
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name))
|
||||
root._publishConnectedChromeState(true);
|
||||
}
|
||||
} else {
|
||||
root._releaseConnectedChromeState();
|
||||
}
|
||||
@@ -376,16 +387,17 @@ Item {
|
||||
Connections {
|
||||
target: ConnectedModeState
|
||||
function onPopoutOwnerIdChanged() {
|
||||
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name) && !ConnectedModeState.hasPopoutOwner(root._chromeClaimId))
|
||||
root._queueFullSync();
|
||||
chromeLease.checkOwnershipRecovery();
|
||||
}
|
||||
function onSurfaceDescriptorsChanged() {
|
||||
chromeLease.checkStateRecovery();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutChanged() {
|
||||
if ((contentWindow.visible || root.shouldBeVisible) && root.screen && PopoutManager.isCurrentPopout(root.popoutHandle, root.screen.name))
|
||||
root._queueFullSync();
|
||||
chromeLease.requestRecovery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,34 +418,26 @@ Item {
|
||||
|
||||
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
|
||||
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;
|
||||
}
|
||||
_lastOpenedScreen = screen;
|
||||
PopoutManager.showPopout(popoutHandle);
|
||||
|
||||
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)
|
||||
morph.openProgress = 0;
|
||||
_captureChromeAnimTravel();
|
||||
}
|
||||
|
||||
if (root.frameOwnsConnectedChrome) {
|
||||
_chromeClaimId = _nextChromeClaimId();
|
||||
chromeLease.beginClaim();
|
||||
_publishConnectedChromeState(true, true);
|
||||
} else {
|
||||
_chromeClaimId = "";
|
||||
chromeLease.release();
|
||||
}
|
||||
|
||||
if (screenChanged) {
|
||||
// Defer the show one event-loop tick. Qt coalesces a synchronous
|
||||
// false→true 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.
|
||||
// Unmap/remap wl_surface across ticks so blur republishes on the new screen.
|
||||
Qt.callLater(() => {
|
||||
if (!root.shouldBeVisible)
|
||||
return;
|
||||
@@ -552,6 +556,18 @@ Item {
|
||||
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) {
|
||||
if (!root.closeFrameGapsActive)
|
||||
return false;
|
||||
@@ -607,7 +623,6 @@ Item {
|
||||
property real renderedAlignedY: alignedY
|
||||
property real renderedAlignedHeight: alignedHeight
|
||||
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
|
||||
|
||||
Behavior on renderedAlignedY {
|
||||
@@ -657,8 +672,6 @@ Item {
|
||||
return 0;
|
||||
if (!root.usesConnectedSurfaceChrome)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -691,16 +704,16 @@ Item {
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
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));
|
||||
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));
|
||||
default:
|
||||
const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
|
||||
const minX = Math.max(edgeGapLeft, adjacentBarClearance(adjacentBarInfo.leftBar));
|
||||
const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, adjacentBarClearance(adjacentBarInfo.rightBar));
|
||||
return Math.max(minX, Math.min(maxX, rawX));
|
||||
const clearLeft = adjacentBarClearance(adjacentBarInfo.leftBar);
|
||||
const clearRight = adjacentBarClearance(adjacentBarInfo.rightBar);
|
||||
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)
|
||||
|
||||
@@ -712,44 +725,34 @@ Item {
|
||||
|
||||
switch (effectiveBarPosition) {
|
||||
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));
|
||||
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));
|
||||
default:
|
||||
const rawY = triggerY - (popupHeight / 2);
|
||||
const minY = Math.max(edgeGapTop, adjacentBarClearance(adjacentBarInfo.topBar));
|
||||
const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, adjacentBarClearance(adjacentBarInfo.bottomBar));
|
||||
return Math.max(minY, Math.min(maxY, rawY));
|
||||
const clearTop = adjacentBarClearance(adjacentBarInfo.topBar);
|
||||
const clearBottom = adjacentBarClearance(adjacentBarInfo.bottomBar);
|
||||
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)
|
||||
|
||||
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
|
||||
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
|
||||
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
|
||||
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
|
||||
readonly property real maskX: _dismissZone.x
|
||||
readonly property real maskY: _dismissZone.y
|
||||
readonly property real maskWidth: _dismissZone.width
|
||||
readonly property real maskHeight: _dismissZone.height
|
||||
|
||||
readonly property real maskX: {
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
|
||||
}
|
||||
|
||||
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);
|
||||
DismissZone {
|
||||
id: _dismissZone
|
||||
barPosition: root.effectiveBarPosition
|
||||
barX: root.barX
|
||||
barY: root.barY
|
||||
barWidth: root.barWidth
|
||||
barHeight: root.barHeight
|
||||
screenWidth: root.screenWidth
|
||||
screenHeight: root.screenHeight
|
||||
adjacentBarInfo: root.adjacentBarInfo
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
@@ -764,10 +767,8 @@ Item {
|
||||
blurEnabled: root.effectiveSurfaceBlurEnabled && !root.frameOwnsConnectedChrome
|
||||
|
||||
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 _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.layer: root.effectivePopoutLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
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;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
|
||||
|
||||
readonly property bool _fullHeight: root.fullHeightSurface
|
||||
anchors {
|
||||
@@ -813,7 +804,6 @@ Item {
|
||||
|
||||
Region {
|
||||
id: contentInputMask
|
||||
// Use bar-aware mask so bar widget clicks pass through when a popout is open.
|
||||
item: (shouldBeVisible && backgroundInteractive) ? backgroundDismissalMask : contentMaskRect
|
||||
}
|
||||
|
||||
@@ -924,7 +914,6 @@ Item {
|
||||
|
||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||
|
||||
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: 0
|
||||
@@ -971,7 +960,6 @@ Item {
|
||||
|
||||
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
|
||||
y: shouldClip ? (contentContainer.barTop ? -connectedClipAllowance : -clipOversize) : 0
|
||||
|
||||
@@ -1008,22 +996,13 @@ Item {
|
||||
|
||||
ElevationShadow {
|
||||
id: shadowSource
|
||||
readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0
|
||||
readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0
|
||||
readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0
|
||||
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
|
||||
visible: !root.usesConnectedSurfaceChrome
|
||||
width: rollOutAdjuster.baseWidth
|
||||
height: rollOutAdjuster.baseHeight
|
||||
opacity: contentWrapper.publishedOpacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x - extraLeft
|
||||
y: contentWrapper.y - extraTop
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
@@ -1035,49 +1014,49 @@ Item {
|
||||
targetColor: contentContainer.surfaceColor
|
||||
borderColor: contentContainer.surfaceBorderColor
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
Item {
|
||||
id: localChrome
|
||||
visible: root.usesLocalConnectedSurfaceChrome
|
||||
|
||||
readonly property real extraLeft: (contentContainer.barTop || contentContainer.barBottom) ? Theme.connectedCornerRadius : 0
|
||||
readonly property real extraTop: (contentContainer.barLeft || contentContainer.barRight) ? Theme.connectedCornerRadius : 0
|
||||
|
||||
readonly property bool shadowsOn: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
readonly property real shadowBlurPx: root.shadowLevel && root.shadowLevel.blurPx !== undefined ? root.shadowLevel.blurPx : 0
|
||||
readonly property real shadowSpreadPx: root.shadowLevel && root.shadowLevel.spreadPx !== undefined ? root.shadowLevel.spreadPx : 0
|
||||
readonly property real shadowOffsetX: Theme.elevationOffsetXFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
|
||||
readonly property real shadowOffsetY: Theme.elevationOffsetYFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset)
|
||||
readonly property color shadowTint: Theme.elevationShadowColor(root.shadowLevel)
|
||||
readonly property var ambient: Theme.elevationAmbient(root.shadowLevel)
|
||||
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
|
||||
|
||||
width: rollOutAdjuster.baseWidth + extraLeft * 2
|
||||
height: rollOutAdjuster.baseHeight + extraTop * 2
|
||||
opacity: contentWrapper.publishedOpacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x - extraLeft
|
||||
y: contentWrapper.y - extraTop
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
visible: root.usesLocalConnectedSurfaceChrome
|
||||
clip: false
|
||||
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")
|
||||
|
||||
Rectangle {
|
||||
x: shadowSource.bodyX
|
||||
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 {
|
||||
visible: root.usesConnectedSurfaceChrome
|
||||
barSide: contentContainer.connectedBarSide
|
||||
placement: "left"
|
||||
spacing: 0
|
||||
connectorRadius: Theme.connectedCornerRadius
|
||||
color: contentContainer.surfaceColor
|
||||
dpr: root.dpr
|
||||
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 {
|
||||
visible: root.usesConnectedSurfaceChrome
|
||||
barSide: contentContainer.connectedBarSide
|
||||
placement: "right"
|
||||
spacing: 0
|
||||
connectorRadius: Theme.connectedCornerRadius
|
||||
color: contentContainer.surfaceColor
|
||||
dpr: root.dpr
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
target: contentWindow
|
||||
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)
|
||||
contentWrapper._renderActive = false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Item {
|
||||
property Component overlayContent: null
|
||||
property alias overlayLoader: overlayLoader
|
||||
readonly property alias backgroundWindow: backgroundWindow
|
||||
readonly property alias contentWindow: contentWindow
|
||||
property real popupWidth: 400
|
||||
property real popupHeight: 300
|
||||
property real triggerX: 0
|
||||
@@ -494,31 +495,21 @@ Item {
|
||||
}
|
||||
})(), dpr)
|
||||
|
||||
readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0
|
||||
readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0
|
||||
readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0
|
||||
readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0
|
||||
readonly property real maskX: _dismissZone.x
|
||||
readonly property real maskY: _dismissZone.y
|
||||
readonly property real maskWidth: _dismissZone.width
|
||||
readonly property real maskHeight: _dismissZone.height
|
||||
|
||||
readonly property real maskX: {
|
||||
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
|
||||
return Math.max(triggeringBarLeftExclusion, adjacentLeftBar);
|
||||
}
|
||||
|
||||
readonly property real maskY: {
|
||||
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
|
||||
return Math.max(triggeringBarTopExclusion, adjacentTopBar);
|
||||
}
|
||||
|
||||
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);
|
||||
DismissZone {
|
||||
id: _dismissZone
|
||||
barPosition: root.effectiveBarPosition
|
||||
barX: root.barX
|
||||
barY: root.barY
|
||||
barWidth: root.barWidth
|
||||
barHeight: root.barHeight
|
||||
screenWidth: root.screenWidth
|
||||
screenHeight: root.screenHeight
|
||||
adjacentBarInfo: root.adjacentBarInfo
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
@@ -598,31 +589,25 @@ Item {
|
||||
id: popoutBlur
|
||||
targetWindow: contentWindow
|
||||
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 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)
|
||||
blurY: trackBlurFromBarEdge ? contentContainer.y + contentContainer.revealY : 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
|
||||
blurHeight: blurAlive ? (trackBlurFromBarEdge ? contentContainer.revealHeight : contentContainer.height * s * op) : 0
|
||||
blurX: revealClipActive ? contentContainer.x : contentContainer.x + contentContainer.width * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
|
||||
blurY: revealClipActive ? contentContainer.y : contentContainer.y + contentContainer.height * (1 - s * op) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
|
||||
blurWidth: root.shouldBeVisible ? (revealClipActive ? contentContainer.width : contentContainer.width * s * op) : 0
|
||||
blurHeight: root.shouldBeVisible ? (revealClipActive ? contentContainer.height : contentContainer.height * s * op) : 0
|
||||
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.layer: root.effectivePopoutLayer
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
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;
|
||||
}
|
||||
WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus)
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
@@ -721,6 +706,8 @@ Item {
|
||||
QtObject {
|
||||
id: morph
|
||||
property real openProgress: 0
|
||||
onOpenProgressChanged: if (root.fluidStandaloneActive)
|
||||
root._kickBlurCommit()
|
||||
Behavior on openProgress {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -16,6 +16,11 @@ Item {
|
||||
property real blurWidth: 0
|
||||
property real blurHeight: 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
|
||||
|
||||
@@ -26,6 +31,14 @@ Item {
|
||||
width: root.blurWidth
|
||||
height: root.blurHeight
|
||||
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() {
|
||||
@@ -39,10 +52,7 @@ Item {
|
||||
targetWindow.BackgroundEffect.blurRegion = null;
|
||||
}
|
||||
|
||||
// Force BackgroundEffect to re-publish the blur region on the current wl_surface.
|
||||
// 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).
|
||||
// Re-publish blur region after wl_surface remaps (e.g. screen change).
|
||||
function kick() {
|
||||
if (!targetWindow)
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user