mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
302 lines
10 KiB
QML
302 lines
10 KiB
QML
pragma Singleton
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import "ConnectedSurfaceDescriptor.js" as SurfaceDescriptor
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property var surfaceDescriptors: ({})
|
|
property var surfaceMotion: ({})
|
|
property var surfaceRevisions: ({})
|
|
property var dockRetractRequests: ({})
|
|
property var popoutMotionHandoff: null
|
|
|
|
// One notifying object per claimed slot keeps animation frames from invalidating every descriptor binding.
|
|
Component {
|
|
id: motionComponent
|
|
|
|
QtObject {
|
|
property var frame: ({
|
|
"bodyX": 0,
|
|
"bodyY": 0,
|
|
"bodyW": 0,
|
|
"bodyH": 0,
|
|
"animX": 0,
|
|
"animY": 0
|
|
})
|
|
}
|
|
}
|
|
|
|
function surfaceSlot(kind, instanceId) {
|
|
const slot = SurfaceDescriptor.slotForKind(kind);
|
|
return instanceId ? slot + ":" + instanceId : slot;
|
|
}
|
|
|
|
function _motionKey(screenName, slot) {
|
|
return screenName + "|" + slot;
|
|
}
|
|
|
|
function _stored(screenName, slot) {
|
|
return screenName ? surfaceDescriptors[screenName]?.[slot] ?? null : null;
|
|
}
|
|
|
|
function surfaceDescriptor(screenName, slot) {
|
|
const base = _stored(screenName, slot) ?? SurfaceDescriptor.empty(slot.split(":")[0], screenName);
|
|
const motion = surfaceMotion[_motionKey(screenName, slot)];
|
|
if (!motion)
|
|
return base;
|
|
return SurfaceDescriptor.normalize(motion.frame, base);
|
|
}
|
|
|
|
function surfaceDescriptorsOfKind(screenName, kind) {
|
|
const screen = screenName ? surfaceDescriptors[screenName] : null;
|
|
if (!screen)
|
|
return [];
|
|
return Object.keys(screen).filter(slot => slot === kind || slot.startsWith(kind + ":")).map(slot => surfaceDescriptor(screenName, slot));
|
|
}
|
|
|
|
function surfaceOwnerId(screenName, slot) {
|
|
return _stored(screenName, slot)?.ownerId ?? "";
|
|
}
|
|
|
|
function hasSurfaceOwner(screenName, slot, ownerId) {
|
|
return !!ownerId && surfaceOwnerId(screenName, slot) === ownerId;
|
|
}
|
|
|
|
function hasSurfaceDescriptor(screenName, slot, ownerId) {
|
|
const descriptor = _stored(screenName, slot);
|
|
return !!descriptor && descriptor.phase !== "hidden" && (!ownerId || descriptor.ownerId === ownerId);
|
|
}
|
|
|
|
function claimSurface(screenName, slot, state, ownerId, exclusive) {
|
|
if (!screenName || !slot || !state || !ownerId)
|
|
return false;
|
|
if (exclusive) {
|
|
for (const name of Object.keys(surfaceDescriptors)) {
|
|
if (name !== screenName)
|
|
releaseSurface(name, slot, "");
|
|
}
|
|
}
|
|
return _writeSurface(screenName, slot, state, ownerId);
|
|
}
|
|
|
|
function updateSurface(screenName, slot, state, ownerId) {
|
|
if (!state || !hasSurfaceOwner(screenName, slot, ownerId))
|
|
return false;
|
|
return _writeSurface(screenName, slot, state, ownerId);
|
|
}
|
|
|
|
function _bumpSurfaceRevision(screenName) {
|
|
surfaceRevisions = Object.assign({}, surfaceRevisions, {
|
|
[screenName]: (surfaceRevisions[screenName] ?? 0) + 1
|
|
});
|
|
}
|
|
|
|
function _writeSurface(screenName, slot, state, ownerId) {
|
|
const stored = _stored(screenName, slot);
|
|
const previous = surfaceDescriptor(screenName, slot);
|
|
let next = SurfaceDescriptor.normalize(Object.assign({}, state, {
|
|
"ownerId": ownerId,
|
|
"screenName": screenName,
|
|
"revision": previous.revision
|
|
}), previous);
|
|
if (SurfaceDescriptor.same(previous, next))
|
|
return true;
|
|
next = SurfaceDescriptor.withRevision(next, previous.revision + 1);
|
|
const screen = Object.assign({}, surfaceDescriptors[screenName]);
|
|
screen[slot] = next;
|
|
surfaceDescriptors = Object.assign({}, surfaceDescriptors, {
|
|
[screenName]: screen
|
|
});
|
|
if (!stored || stored.ownerId !== next.ownerId || stored.visible !== next.visible)
|
|
_bumpSurfaceRevision(screenName);
|
|
const key = _motionKey(screenName, slot);
|
|
const motion = surfaceMotion[key] ?? motionComponent.createObject(root);
|
|
motion.frame = {
|
|
"bodyX": next.bodyRect.x,
|
|
"bodyY": next.bodyRect.y,
|
|
"bodyW": next.bodyRect.width,
|
|
"bodyH": next.bodyRect.height,
|
|
"animX": next.animationOffset.x,
|
|
"animY": next.animationOffset.y
|
|
};
|
|
if (!surfaceMotion[key])
|
|
surfaceMotion = Object.assign({}, surfaceMotion, {
|
|
[key]: motion
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function releaseSurface(screenName, slot, ownerId) {
|
|
const current = _stored(screenName, slot);
|
|
if (!current || (ownerId && current.ownerId !== ownerId))
|
|
return false;
|
|
const screen = Object.assign({}, surfaceDescriptors[screenName]);
|
|
delete screen[slot];
|
|
const next = Object.assign({}, surfaceDescriptors);
|
|
if (Object.keys(screen).length)
|
|
next[screenName] = screen;
|
|
else
|
|
delete next[screenName];
|
|
surfaceDescriptors = next;
|
|
_dropMotion([_motionKey(screenName, slot)]);
|
|
_bumpSurfaceRevision(screenName);
|
|
return true;
|
|
}
|
|
|
|
function setSurfaceMotion(screenName, slot, ownerId, patch) {
|
|
if (!patch || !hasSurfaceOwner(screenName, slot, ownerId))
|
|
return false;
|
|
const motion = surfaceMotion[_motionKey(screenName, slot)];
|
|
if (!motion)
|
|
return false;
|
|
let frame = null;
|
|
for (const key in patch) {
|
|
const value = Number(patch[key]);
|
|
if (isNaN(value) || !(key in motion.frame) || motion.frame[key] === value)
|
|
continue;
|
|
frame = frame ?? Object.assign({}, motion.frame);
|
|
frame[key] = value;
|
|
}
|
|
if (frame)
|
|
motion.frame = frame;
|
|
return true;
|
|
}
|
|
|
|
function _dropMotion(keys) {
|
|
const dropped = keys.filter(key => surfaceMotion[key]);
|
|
if (!dropped.length)
|
|
return;
|
|
const next = Object.assign({}, surfaceMotion);
|
|
for (const key of dropped) {
|
|
next[key].destroy();
|
|
delete next[key];
|
|
}
|
|
surfaceMotion = next;
|
|
}
|
|
|
|
function savePopoutMotion(ownerId, screenName, barSide, rect, velocity) {
|
|
if (!hasSurfaceOwner(screenName, "popout", ownerId))
|
|
return;
|
|
popoutMotionHandoff = {
|
|
"ownerId": ownerId,
|
|
"screen": screenName,
|
|
"barSide": barSide,
|
|
"rect": Qt.rect(rect.x, rect.y, rect.width, rect.height),
|
|
"velocity": Qt.vector4d(velocity.x, velocity.y, velocity.z, velocity.w)
|
|
};
|
|
}
|
|
|
|
function takePopoutMotion(screenName, barSide) {
|
|
const handoff = popoutMotionHandoff;
|
|
popoutMotionHandoff = null;
|
|
if (!handoff || handoff.screen !== screenName || handoff.barSide !== barSide || !hasSurfaceOwner(screenName, "popout", handoff.ownerId))
|
|
return null;
|
|
return handoff;
|
|
}
|
|
|
|
function requestDockRetract(requesterId, screenName, side) {
|
|
if (!requesterId || !screenName || !side)
|
|
return false;
|
|
const existing = dockRetractRequests[requesterId];
|
|
if (existing && existing.screenName === screenName && existing.side === side)
|
|
return true;
|
|
dockRetractRequests = Object.assign({}, dockRetractRequests, {
|
|
[requesterId]: {
|
|
"screenName": screenName,
|
|
"side": side
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function releaseDockRetract(requesterId) {
|
|
if (!requesterId || !dockRetractRequests[requesterId])
|
|
return false;
|
|
const next = Object.assign({}, dockRetractRequests);
|
|
delete next[requesterId];
|
|
dockRetractRequests = next;
|
|
return true;
|
|
}
|
|
|
|
function dockRetractActiveForSide(screenName, side) {
|
|
if (!screenName || !side)
|
|
return false;
|
|
return Object.values(dockRetractRequests).some(request => request.screenName === screenName && request.side === side);
|
|
}
|
|
|
|
function _pruneKeyed(dict, keep) {
|
|
const next = {};
|
|
let changed = false;
|
|
for (const key in dict) {
|
|
if (keep(key, dict[key]))
|
|
next[key] = dict[key];
|
|
else
|
|
changed = true;
|
|
}
|
|
return changed ? next : null;
|
|
}
|
|
|
|
function _pruneOrphanDocks() {
|
|
if (typeof SettingsData === "undefined" || !SettingsData.dockConfigs)
|
|
return;
|
|
const validDockIds = new Set((SettingsData.dockConfigs || []).map(config => config?.id).filter(Boolean));
|
|
for (const screenName of Object.keys(surfaceDescriptors)) {
|
|
const screen = surfaceDescriptors[screenName];
|
|
if (!screen)
|
|
continue;
|
|
for (const slot of Object.keys(screen)) {
|
|
if (slot.startsWith("dock:")) {
|
|
const dockId = slot.substring(5);
|
|
if (!validDockIds.has(dockId))
|
|
releaseSurface(screenName, slot, "");
|
|
} else if (slot === "dock" && !validDockIds.has("")) {
|
|
releaseSurface(screenName, slot, "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _pruneToLiveScreens() {
|
|
const live = new Set((Quickshell.screens || []).map(screen => screen?.name).filter(Boolean));
|
|
const descriptors = _pruneKeyed(surfaceDescriptors, name => live.has(name));
|
|
if (descriptors)
|
|
surfaceDescriptors = descriptors;
|
|
const revisions = _pruneKeyed(surfaceRevisions, name => live.has(name));
|
|
if (revisions)
|
|
surfaceRevisions = revisions;
|
|
const retract = _pruneKeyed(dockRetractRequests, (_, request) => live.has(request.screenName));
|
|
if (retract)
|
|
dockRetractRequests = retract;
|
|
_dropMotion(Object.keys(surfaceMotion).filter(key => !live.has(key.split("|")[0])));
|
|
_pruneOrphanDocks();
|
|
}
|
|
|
|
Connections {
|
|
target: Quickshell
|
|
function onScreensChanged() {
|
|
screenPruneAction.schedule();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onDockConfigsChanged() {
|
|
dockPruneAction.schedule();
|
|
}
|
|
}
|
|
|
|
DeferredAction {
|
|
id: screenPruneAction
|
|
onTriggered: root._pruneToLiveScreens()
|
|
}
|
|
|
|
DeferredAction {
|
|
id: dockPruneAction
|
|
onTriggered: root._pruneOrphanDocks()
|
|
}
|
|
}
|